dd-trace
Version:
Datadog APM tracing client for JavaScript
180 lines (152 loc) • 5.17 kB
JavaScript
const constants = require('./constants')
const tags = require('../../../ext/tags')
const id = require('./id')
const { isError } = require('./util')
const SAMPLING_PRIORITY_KEY = constants.SAMPLING_PRIORITY_KEY
const SAMPLING_RULE_DECISION = constants.SAMPLING_RULE_DECISION
const SAMPLING_LIMIT_DECISION = constants.SAMPLING_LIMIT_DECISION
const SAMPLING_AGENT_DECISION = constants.SAMPLING_AGENT_DECISION
const MEASURED = tags.MEASURED
const ORIGIN_KEY = constants.ORIGIN_KEY
const HOSTNAME_KEY = constants.HOSTNAME_KEY
const map = {
'service.name': 'service',
'span.type': 'type',
'resource.name': 'resource'
}
function format (span) {
const formatted = formatSpan(span)
extractError(formatted, span)
extractRootTags(formatted, span)
extractChunkTags(formatted, span)
extractTags(formatted, span)
return formatted
}
function formatSpan (span) {
const spanContext = span.context()
return {
trace_id: spanContext._traceId,
span_id: spanContext._spanId,
parent_id: spanContext._parentId || id('0'),
name: String(spanContext._name),
resource: String(spanContext._name),
error: 0,
meta: {},
metrics: {},
start: Math.round(span._startTime * 1e6),
duration: Math.round(span._duration * 1e6)
}
}
function extractTags (trace, span) {
const context = span.context()
const origin = context._trace.origin
const tags = context._tags
const hostname = context._hostname
const priority = context._sampling.priority
if (tags['span.kind'] && tags['span.kind'] !== 'internal') {
addTag({}, trace.metrics, MEASURED, 1)
}
for (const tag in tags) {
switch (tag) {
case 'service.name':
case 'span.type':
case 'resource.name':
addTag(trace, {}, map[tag], tags[tag])
break
// HACK: remove when Datadog supports numeric status code
case 'http.status_code':
addTag(trace.meta, {}, tag, tags[tag] && String(tags[tag]))
break
case HOSTNAME_KEY:
case MEASURED:
addTag({}, trace.metrics, tag, tags[tag] === undefined || tags[tag] ? 1 : 0)
break
case 'error':
if (tags[tag] && (context._name !== 'fs.operation')) {
trace.error = 1
}
break
case 'error.type':
case 'error.msg':
case 'error.stack':
// HACK: remove when implemented in the backend
if (context._name !== 'fs.operation') {
trace.error = 1
}
default: // eslint-disable-line no-fallthrough
addTag(trace.meta, trace.metrics, tag, tags[tag])
}
}
if (span.tracer()._service === tags['service.name']) {
addTag(trace.meta, trace.metrics, 'language', 'javascript')
}
addTag(trace.meta, trace.metrics, SAMPLING_PRIORITY_KEY, priority)
addTag(trace.meta, trace.metrics, ORIGIN_KEY, origin)
addTag(trace.meta, trace.metrics, HOSTNAME_KEY, hostname)
}
function extractRootTags (trace, span) {
const context = span.context()
const isLocalRoot = span === context._trace.started[0]
const parentId = context._parentId
if (!isLocalRoot || (parentId && parentId.toString(10) !== '0')) return
addTag({}, trace.metrics, SAMPLING_RULE_DECISION, context._trace[SAMPLING_RULE_DECISION])
addTag({}, trace.metrics, SAMPLING_LIMIT_DECISION, context._trace[SAMPLING_LIMIT_DECISION])
addTag({}, trace.metrics, SAMPLING_AGENT_DECISION, context._trace[SAMPLING_AGENT_DECISION])
}
function extractChunkTags (trace, span) {
const context = span.context()
const isLocalRoot = span === context._trace.started[0]
if (!isLocalRoot) return
for (const key in context._trace.tags) {
addTag(trace.meta, trace.metrics, key, context._trace.tags[key])
}
}
function extractError (trace, span) {
const error = span.context()._tags['error']
if (isError(error)) {
addTag(trace.meta, trace.metrics, 'error.msg', error.message)
addTag(trace.meta, trace.metrics, 'error.type', error.name)
addTag(trace.meta, trace.metrics, 'error.stack', error.stack)
}
}
function addTag (meta, metrics, key, value, nested) {
switch (typeof value) {
case 'string':
if (!value) break
meta[key] = value
break
case 'number':
if (isNaN(value)) break
metrics[key] = value
break
case 'boolean':
metrics[key] = value ? 1 : 0
break
case 'undefined':
break
case 'object':
if (value === null) break
// Special case for Node.js Buffer and URL
if (isNodeBuffer(value) || isUrl(value)) {
metrics[key] = value.toString()
} else if (!Array.isArray(value) && !nested) {
for (const prop in value) {
if (!value.hasOwnProperty(prop)) continue
addTag(meta, metrics, `${key}.${prop}`, value[prop], true)
}
}
break
}
}
function isNodeBuffer (obj) {
return obj.constructor && obj.constructor.name === 'Buffer' &&
typeof obj.readInt8 === 'function' &&
typeof obj.toString === 'function'
}
function isUrl (obj) {
return obj.constructor && obj.constructor.name === 'URL' &&
typeof obj.href === 'string' &&
typeof obj.toString === 'function'
}
module.exports = format