Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions LICENSE-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ require,@datadog/native-iast-rewriter,Apache license 2.0,Copyright 2018 Datadog
require,@datadog/native-iast-taint-tracking,Apache license 2.0,Copyright 2018 Datadog Inc.
require,@datadog/pprof,Apache license 2.0,Copyright 2019 Google Inc.
require,@datadog/sketches-js,Apache license 2.0,Copyright 2020 Datadog Inc.
require,@opentelemetry/api,Apache license 2.0,Copyright OpenTelemetry Authors
require,@opentelemetry/core,Apache license 2.0,Copyright OpenTelemetry Authors
require,crypto-randomuuid,MIT,Copyright 2021 Node.js Foundation and contributors
require,diagnostics_channel,MIT,Copyright 2021 Simon D.
require,ignore,MIT,Copyright 2013 Kael Zhang and contributors
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"test:appsec:ci": "nyc --no-clean --include \"packages/dd-trace/src/appsec/**/*.js\" --exclude \"packages/dd-trace/test/appsec/**/*.plugin.spec.js\" -- npm run test:appsec",
"test:appsec:plugins": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/appsec/**/*.@($(echo $PLUGINS)).plugin.spec.js\"",
"test:appsec:plugins:ci": "yarn services && nyc --no-clean --include \"packages/dd-trace/test/appsec/**/*.@($(echo $PLUGINS)).plugin.spec.js\" -- npm run test:appsec:plugins",
"test:trace:core": "tap packages/dd-trace/test/*.spec.js \"packages/dd-trace/test/{ci-visibility,encode,exporters,opentracing,plugins,telemetry}/**/*.spec.js\"",
"test:trace:core": "tap packages/dd-trace/test/*.spec.js \"packages/dd-trace/test/{ci-visibility,encode,exporters,opentelemetry,opentracing,plugins,telemetry}/**/*.spec.js\"",
"test:trace:core:ci": "npm run test:trace:core -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/**/*.js\"",
"test:instrumentations": "mocha --colors -r 'packages/dd-trace/test/setup/mocha.js' 'packages/datadog-instrumentations/test/**/*.spec.js'",
"test:instrumentations:ci": "nyc --no-clean --include 'packages/datadog-instrumentations/src/**/*.js' -- npm run test:instrumentations",
Expand Down Expand Up @@ -72,6 +72,8 @@
"@datadog/native-metrics": "^2.0.0",
"@datadog/pprof": "^2.2.1",
"@datadog/sketches-js": "^2.1.0",
"@opentelemetry/api": "^1.4.1",
"@opentelemetry/core": "^1.10.1",
"crypto-randomuuid": "^1.0.0",
"diagnostics_channel": "^1.1.0",
"ignore": "^5.2.0",
Expand Down
67 changes: 67 additions & 0 deletions packages/dd-trace/src/opentelemetry/context_manager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use strict'

const { AsyncLocalStorage } = require('async_hooks')
const { trace, ROOT_CONTEXT } = require('@opentelemetry/api')

const SpanContext = require('./span_context')
const tracer = require('../../')

// Horrible hack to acquire the otherwise inaccessible SPAN_KEY so we can redirect it...
let SPAN_KEY
trace.getSpan({
getValue (key) {
SPAN_KEY = key
}
})

function wrappedGetValue (target) {
return (key) => {
if (key === SPAN_KEY) {
return {
spanContext () {
const activeSpan = tracer.scope().active()
const context = activeSpan && activeSpan.context()
return new SpanContext(context)
}
}
}
return target.getValue(key)
}
}

class ContextManager {
constructor () {
this._store = new AsyncLocalStorage()
}

active () {
const active = this._store.getStore() || ROOT_CONTEXT

return new Proxy(active, {
get (target, key) {
return key === 'getValue' ? wrappedGetValue(target) : target[key]
}
})
}

with (context, fn, thisArg, ...args) {
const span = trace.getSpan(context)
const ddScope = tracer.scope()
return ddScope.activate(span._ddSpan, () => {
const cb = thisArg == null ? fn : fn.bind(thisArg)
return this._store.run(context, cb, ...args)
})
}

bind (context, target) {
const self = this
return function (...args) {
return self.with(context, target, this, args)
}
}

enable () {}
disable () {}
}

module.exports = ContextManager
16 changes: 16 additions & 0 deletions packages/dd-trace/src/opentelemetry/sampler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict'

class Sampler {
shouldSample (context, traceId, spanName, spanKind, attributes, links) {
// 0 = no, 1 = record, 2 = record and sample
// TODO: Make this actually do sampling...
return { decision: 2 }
}

/** Returns the sampler name or short description with the configuration. */
toString () {
return 'DatadogSampler'
}
}

module.exports = Sampler
152 changes: 152 additions & 0 deletions packages/dd-trace/src/opentelemetry/span.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
'use strict'

const api = require('@opentelemetry/api')

const {
getTimeOrigin,
otperformance,
timeInputToHrTime
} = require('@opentelemetry/core')

const tracer = require('../../')
const DatadogSpan = require('../opentracing/span')
const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../constants')

const SpanContext = require('./span_context')

// The one built into OTel rounds so we lose sub-millisecond precision.
function hrTimeToMilliseconds (time) {
return time[0] * 1e3 + time[1] / 1e6
}

class Span {
constructor (
parentTracer,
context,
spanName,
spanContext,
kind,
links = [],
timeInput
) {
const { _tracer } = tracer

const hrStartTime = timeInputToHrTime(timeInput || (otperformance.now() + getTimeOrigin()))
const startTime = hrTimeToMilliseconds(hrStartTime)

this._ddSpan = new DatadogSpan(_tracer, _tracer._processor, _tracer._prioritySampler, {
operationName: spanName,
context: spanContext._ddContext,
startTime,
hostname: _tracer._hostname,
tags: {
'service.name': _tracer._service
}
}, _tracer._debug)

this._parentTracer = parentTracer
this._context = context

this._hasStatus = false

// NOTE: Need to grab the value before setting it on the span because the
// math for computing opentracing timestamps is apparently lossy...
this.startTime = hrStartTime
this.kind = kind
this.links = links
this._spanProcessor.onStart(this, context)
}

get parentSpanId () {
const { _parentId } = this._ddSpan.context()
return _parentId && _parentId.toString(16)
}

// Expected by OTel
get resource () {
return this._parentTracer.resource
}
get instrumentationLibrary () {
return this._parentTracer.instrumentationLibrary
}
get _spanProcessor () {
return this._parentTracer.getActiveSpanProcessor()
}

get name () {
return this._ddSpan.context()._name
}

spanContext () {
return new SpanContext(this._ddSpan.context())
}

setAttribute (key, value) {
this._ddSpan.setTag(key, value)
return this
}

setAttributes (attributes) {
this._ddSpan.addTags(attributes)
return this
}

addEvent (name, attributesOrStartTime, startTime) {
api.diag.warn('Events not supported')
return this
}

setStatus ({ code, message }) {
if (!this.ended && !this._hasStatus && code) {
this._hasStatus = true
if (code === 2) {
this._ddSpan.addTags({
[ERROR_MESSAGE]: message
})
}
}
return this
}

updateName (name) {
if (!this.ended) {
this._ddSpan.setOperationName(name)
}
return this
}

end (timeInput) {
if (this.ended) {
api.diag.error('You can only call end() on a span once.')
return
}

const hrEndTime = timeInputToHrTime(timeInput || (otperformance.now() + getTimeOrigin()))
const endTime = hrTimeToMilliseconds(hrEndTime)

this._ddSpan.finish(endTime)
this._spanProcessor.onEnd(this)
}

isRecording () {
return this.ended === false
}

recordException (exception) {
this._ddSpan.addTags({
[ERROR_TYPE]: exception.name,
[ERROR_MESSAGE]: exception.message,
[ERROR_STACK]: exception.stack
})
}

get duration () {
return this._ddSpan._duration
}

get ended () {
return typeof this.duration !== 'undefined'
}
}

module.exports = Span
44 changes: 44 additions & 0 deletions packages/dd-trace/src/opentelemetry/span_context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict'

const api = require('@opentelemetry/api')
const { AUTO_KEEP } = require('../../../../ext/priority')
const DatadogSpanContext = require('../opentracing/span_context')
const id = require('../id')

function newContext () {
const spanId = id()
return new DatadogSpanContext({
traceId: spanId,
spanId
})
}

class SpanContext {
constructor (context) {
if (!(context instanceof DatadogSpanContext)) {
context = context
? new DatadogSpanContext(context)
: newContext()
}
this._ddContext = context
}

get traceId () {
return this._ddContext._traceId.toString(16)
}

get spanId () {
return this._ddContext._spanId.toString(16)
}

get traceFlags () {
return this._ddContext._sampling.priority >= AUTO_KEEP ? 1 : 0
}

get traceState () {
const ts = this._ddContext._tracestate
return api.createTraceState(ts ? ts.toString() : '')
}
}

module.exports = SpanContext
50 changes: 50 additions & 0 deletions packages/dd-trace/src/opentelemetry/span_processor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict'

class NoopSpanProcessor {
forceFlush () {
return Promise.resolve()
}

onStart (span, context) { }
onEnd (span) { }

shutdown () {
return Promise.resolve()
}
}

class MultiSpanProcessor extends NoopSpanProcessor {
constructor (spanProcessors) {
super()
this._processors = spanProcessors
}

forceFlush () {
return Promise.all(
this._processors.map(p => p.forceFlush())
)
}

onStart (span, context) {
for (const processor of this._processors) {
processor.onStart(span, context)
}
}

onEnd (span) {
for (const processor of this._processors) {
processor.onEnd(span)
}
}

shutdown () {
return Promise.all(
this._processors.map(p => p.shutdown())
)
}
}

module.exports = {
MultiSpanProcessor,
NoopSpanProcessor
}
Loading