-
Notifications
You must be signed in to change notification settings - Fork 309
/
span.js
247 lines (205 loc) · 6.21 KB
/
span.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
'use strict'
const api = require('@opentelemetry/api')
const { performance } = require('perf_hooks')
const { timeOrigin } = performance
const { timeInputToHrTime } = require('@opentelemetry/core')
const tracer = require('../../')
const DatadogSpan = require('../opentracing/span')
const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../constants')
const { SERVICE_NAME, RESOURCE_NAME } = require('../../../../ext/tags')
const kinds = require('../../../../ext/kinds')
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
}
const spanKindNames = {
[api.SpanKind.INTERNAL]: kinds.INTERNAL,
[api.SpanKind.SERVER]: kinds.SERVER,
[api.SpanKind.CLIENT]: kinds.CLIENT,
[api.SpanKind.PRODUCER]: kinds.PRODUCER,
[api.SpanKind.CONSUMER]: kinds.CONSUMER
}
/**
* Several of these attributes are not yet supported by the Node.js OTel API.
* We check for old equivalents where we can, but not all had equivalents.
*/
function spanNameMapper (spanName, kind, attributes) {
if (spanName) return spanName
const opName = attributes['operation.name']
if (opName) return opName
const { INTERNAL, SERVER, CLIENT } = api.SpanKind
// HTTP server and client requests
// TODO: Drop http.method when http.request.method is supported.
for (const key of ['http.method', 'http.request.method']) {
if (key in attributes) {
if (kind === SERVER) {
return 'http.server.request'
}
if (kind === CLIENT) {
return 'http.client.request'
}
}
}
// Databases
const dbSystem = attributes['db.system']
if (dbSystem && kind === CLIENT) {
return `${dbSystem}.query`
}
// Messaging
const msgSys = attributes['messaging.system']
const msgOp = attributes['messaging.operation']
if (msgSys && msgOp && kind !== INTERNAL) {
return `${msgSys}.${msgOp}`
}
// RPC (and AWS)
const rpcSystem = attributes['rpc.system']
if (rpcSystem) {
if (kind === CLIENT) {
return rpcSystem === 'aws-api'
? `aws.${attributes['rpc.service'] || 'client'}.request`
: `${rpcSystem}.client.request`
}
if (kind === SERVER) {
return `${rpcSystem}.server.request`
}
}
// FaaS
const faasProvider = attributes['faas.invoked_provider']
const faasName = attributes['faas.invoked_name']
const faasTrigger = attributes['faas.trigger']
if (kind === CLIENT && faasProvider && faasName) {
return `${faasProvider}.${faasName}.invoke`
}
if (kind === SERVER && faasTrigger) {
return `${faasTrigger}.invoke`
}
// GraphQL
// NOTE: Not part of Semantic Convention spec yet, but is used in the GraphQL
// integration.
const isGraphQL = 'graphql.operation.type' in attributes
if (isGraphQL) return 'graphql.server.request'
// Network
// TODO: Doesn't exist yet. No equivalent.
const protocol = attributes['network.protocol.name']
const protocolPrefix = protocol ? `${protocol}.` : ''
if (kind === SERVER) return `${protocolPrefix}server.request`
if (kind === CLIENT) return `${protocolPrefix}client.request`
// If all else fails, default to stringified span.kind.
return spanKindNames[kind]
}
class Span {
constructor (
parentTracer,
context,
spanName,
spanContext,
kind,
links = [],
timeInput,
attributes
) {
const { _tracer } = tracer
const hrStartTime = timeInputToHrTime(timeInput || (performance.now() + timeOrigin))
const startTime = hrTimeToMilliseconds(hrStartTime)
this._ddSpan = new DatadogSpan(_tracer, _tracer._processor, _tracer._prioritySampler, {
operationName: spanNameMapper(spanName, kind, attributes),
context: spanContext._ddContext,
startTime,
hostname: _tracer._hostname,
integrationName: 'otel',
tags: {
[SERVICE_NAME]: _tracer._service,
[RESOURCE_NAME]: spanName
}
}, _tracer._debug)
if (attributes) {
this.setAttributes(attributes)
}
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 || (performance.now() + timeOrigin))
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