UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

128 lines (109 loc) 4.2 kB
'use strict' // encoding used here is sha256 // other languages use FNV1 // this inconsistency is ok because hashes do not need to be consistent across services const crypto = require('crypto') const { encodeVarint, decodeVarint } = require('./encoding') const { LRUCache } = require('lru-cache') const log = require('../log') const pick = require('../../../datadog-core/src/utils/src/pick') const cache = new LRUCache({ max: 500 }) const CONTEXT_PROPAGATION_KEY = 'dd-pathway-ctx' const CONTEXT_PROPAGATION_KEY_BASE64 = 'dd-pathway-ctx-base64' const logKeys = [CONTEXT_PROPAGATION_KEY, CONTEXT_PROPAGATION_KEY_BASE64] function shaHash (checkpointString) { const hash = crypto.createHash('sha256').update(checkpointString).digest('hex').slice(0, 16) return Buffer.from(hash, 'hex') } function computeHash (service, env, edgeTags, parentHash) { edgeTags.sort() const hashableEdgeTags = edgeTags.filter(item => item !== 'manual_checkpoint:true') const key = `${service}${env}${hashableEdgeTags.join('')}${parentHash}` let value = cache.get(key) if (value) { return value } const currentHash = shaHash(`${service}${env}` + hashableEdgeTags.join('')) const buf = Buffer.concat([currentHash, parentHash], 16) value = shaHash(buf.toString()) cache.set(key, value) return value } function encodePathwayContext (dataStreamsContext) { return Buffer.concat([ dataStreamsContext.hash, Buffer.from(encodeVarint(Math.round(dataStreamsContext.pathwayStartNs / 1e6))), Buffer.from(encodeVarint(Math.round(dataStreamsContext.edgeStartNs / 1e6))) ], 20) } function encodePathwayContextBase64 (dataStreamsContext) { const encodedPathway = encodePathwayContext(dataStreamsContext) return encodedPathway.toString('base64') } function decodePathwayContext (pathwayContext) { if (pathwayContext == null || pathwayContext.length < 8) { return null } // hash and parent hash are in LE const pathwayHash = pathwayContext.subarray(0, 8) const encodedTimestamps = pathwayContext.subarray(8) const [pathwayStartMs, encodedTimeSincePrev] = decodeVarint(encodedTimestamps) if (pathwayStartMs === undefined) { return null } const [edgeStartMs] = decodeVarint(encodedTimeSincePrev) if (edgeStartMs === undefined) { return null } return { hash: pathwayHash, pathwayStartNs: pathwayStartMs * 1e6, edgeStartNs: edgeStartMs * 1e6 } } function decodePathwayContextBase64 (pathwayContext) { if (pathwayContext == null || pathwayContext.length < 8) { return } if (Buffer.isBuffer(pathwayContext)) { pathwayContext = pathwayContext.toString() } const encodedPathway = Buffer.from(pathwayContext, 'base64') return decodePathwayContext(encodedPathway) } const DsmPathwayCodec = { // we use a class for encoding / decoding in case we update our encoding/decoding. A class will make updates easier // instead of using individual functions. encode (dataStreamsContext, carrier) { if (!dataStreamsContext || !dataStreamsContext.hash) { return } carrier[CONTEXT_PROPAGATION_KEY_BASE64] = encodePathwayContextBase64(dataStreamsContext) log.debug(() => `Injected into DSM carrier: ${JSON.stringify(pick(carrier, logKeys))}.`) }, decode (carrier) { log.debug(() => `Attempting extract from DSM carrier: ${JSON.stringify(pick(carrier, logKeys))}.`) if (carrier == null) return let ctx if (CONTEXT_PROPAGATION_KEY_BASE64 in carrier) { // decode v2 encoding of base64 ctx = decodePathwayContextBase64(carrier[CONTEXT_PROPAGATION_KEY_BASE64]) } else if (CONTEXT_PROPAGATION_KEY in carrier) { try { // decode v1 encoding ctx = decodePathwayContext(carrier[CONTEXT_PROPAGATION_KEY]) } catch { // pass } // cover case where base64 context was received under wrong key if (!ctx && CONTEXT_PROPAGATION_KEY in carrier) { ctx = decodePathwayContextBase64(carrier[CONTEXT_PROPAGATION_KEY]) } } return ctx } } module.exports = { computePathwayHash: computeHash, encodePathwayContext, decodePathwayContext, encodePathwayContextBase64, decodePathwayContextBase64, DsmPathwayCodec }