From e7635cdd5959a760ecff015220f9bdc40e568233 Mon Sep 17 00:00:00 2001 From: Ugaitz Urien Date: Thu, 16 May 2024 16:53:34 +0200 Subject: [PATCH] Support meta_struct in v0.4 (#4287) --- packages/dd-trace/src/encode/0.4.js | 53 +++++- packages/dd-trace/test/encode/0.4.spec.js | 219 ++++++++++++++++++++++ packages/dd-trace/test/encode/0.5.spec.js | 27 +++ 3 files changed, 296 insertions(+), 3 deletions(-) diff --git a/packages/dd-trace/src/encode/0.4.js b/packages/dd-trace/src/encode/0.4.js index 7cf1ee0b2ef..11cf8c4b6c7 100644 --- a/packages/dd-trace/src/encode/0.4.js +++ b/packages/dd-trace/src/encode/0.4.js @@ -83,13 +83,17 @@ class AgentEncoder { span = formatSpan(span) bytes.reserve(1) - if (span.type) { + if (span.type && span.meta_struct) { + bytes.buffer[bytes.length++] = 0x8d + } else if (span.type || span.meta_struct) { bytes.buffer[bytes.length++] = 0x8c + } else { + bytes.buffer[bytes.length++] = 0x8b + } + if (span.type) { this._encodeString(bytes, 'type') this._encodeString(bytes, span.type) - } else { - bytes.buffer[bytes.length++] = 0x8b } this._encodeString(bytes, 'trace_id') @@ -114,6 +118,10 @@ class AgentEncoder { this._encodeMap(bytes, span.meta) this._encodeString(bytes, 'metrics') this._encodeMap(bytes, span.metrics) + if (span.meta_struct) { + this._encodeString(bytes, 'meta_struct') + this._encodeObject(bytes, span.meta_struct) + } } } @@ -263,6 +271,45 @@ class AgentEncoder { } } + _encodeObject (bytes, value, circularReferencesDetector = new Set()) { + circularReferencesDetector.add(value) + if (Array.isArray(value)) { + return this._encodeObjectAsArray(bytes, value, circularReferencesDetector) + } else if (value !== null && typeof value === 'object') { + return this._encodeObjectAsMap(bytes, value, circularReferencesDetector) + } else if (typeof value === 'string' || typeof value === 'number') { + this._encodeValue(bytes, value) + } + } + + _encodeObjectAsMap (bytes, value, circularReferencesDetector) { + const keys = Object.keys(value) + const validKeys = keys.filter(key => + typeof value[key] === 'string' || + typeof value[key] === 'number' || + (value[key] !== null && typeof value[key] === 'object' && !circularReferencesDetector.has(value[key]))) + + this._encodeMapPrefix(bytes, validKeys.length) + + for (const key of validKeys) { + this._encodeString(bytes, key) + this._encodeObject(bytes, value[key], circularReferencesDetector) + } + } + + _encodeObjectAsArray (bytes, value, circularReferencesDetector) { + const validValue = value.filter(item => + typeof item === 'string' || + typeof item === 'number' || + (item !== null && typeof item === 'object' && !circularReferencesDetector.has(item))) + + this._encodeArrayPrefix(bytes, validValue) + + for (const item of validValue) { + this._encodeObject(bytes, item, circularReferencesDetector) + } + } + _cacheString (value) { if (!(value in this._stringMap)) { this._stringCount++ diff --git a/packages/dd-trace/test/encode/0.4.spec.js b/packages/dd-trace/test/encode/0.4.spec.js index 812053972bb..cd20a318e4d 100644 --- a/packages/dd-trace/test/encode/0.4.spec.js +++ b/packages/dd-trace/test/encode/0.4.spec.js @@ -235,4 +235,223 @@ describe('encode', () => { expect(trace[0].meta).to.deep.equal({ bar: 'baz', '_dd.span_links': encodedLink }) expect(trace[0].metrics).to.deep.equal({ example: 1 }) }) + + describe('meta_struct', () => { + it('should encode meta_struct with simple key value object', () => { + const metaStruct = { + foo: 'bar', + baz: 123 + } + data[0].meta_struct = metaStruct + encoder.encode(data) + + const buffer = encoder.makePayload() + + const decoded = msgpack.decode(buffer, { codec }) + const trace = decoded[0] + expect(trace[0].meta_struct).to.deep.equal(metaStruct) + }) + + it('should encode meta_struct with simple array of simple values', () => { + const metaStruct = ['one', 2, 'three', 4, 5, 'six'] + data[0].meta_struct = metaStruct + encoder.encode(data) + + const buffer = encoder.makePayload() + + const decoded = msgpack.decode(buffer, { codec }) + const trace = decoded[0] + expect(trace[0].meta_struct).to.deep.equal(metaStruct) + }) + + it('should encode meta_struct with array of objects', () => { + const metaStruct = [{ foo: 'bar' }, { baz: 123 }] + data[0].meta_struct = metaStruct + encoder.encode(data) + + const buffer = encoder.makePayload() + + const decoded = msgpack.decode(buffer, { codec }) + const trace = decoded[0] + expect(trace[0].meta_struct).to.deep.equal(metaStruct) + }) + + it('should encode meta_struct with empty object and array', () => { + const metaStruct = { + foo: {}, + bar: [] + } + data[0].meta_struct = metaStruct + encoder.encode(data) + + const buffer = encoder.makePayload() + + const decoded = msgpack.decode(buffer, { codec }) + const trace = decoded[0] + expect(trace[0].meta_struct).to.deep.equal(metaStruct) + }) + + it('should encode meta_struct with possible real use case', () => { + const metaStruct = { + '_dd.stack': { + exploit: [ + { + type: 'test', + language: 'nodejs', + id: 'someuuid', + message: 'Threat detected', + frames: [ + { + id: 0, + file: 'test.js', + line: 1, + column: 31, + function: 'test' + }, + { + id: 1, + file: 'test2.js', + line: 54, + column: 77, + function: 'test' + }, + { + id: 2, + file: 'test.js', + line: 1245, + column: 41, + function: 'test' + }, + { + id: 3, + file: 'test3.js', + line: 2024, + column: 32, + function: 'test' + } + ] + } + ] + } + } + data[0].meta_struct = metaStruct + + encoder.encode(data) + + const buffer = encoder.makePayload() + + const decoded = msgpack.decode(buffer, { codec }) + const trace = decoded[0] + expect(trace[0].meta_struct).to.deep.equal(metaStruct) + }) + + it('should encode meta_struct ignoring circular references in objects', () => { + const circular = { + bar: 'baz', + deeper: { + foo: 'bar' + } + } + circular.deeper.circular = circular + const metaStruct = { + foo: circular + } + data[0].meta_struct = metaStruct + + encoder.encode(data) + + const buffer = encoder.makePayload() + + const decoded = msgpack.decode(buffer, { codec }) + const trace = decoded[0] + + const expectedMetaStruct = { + foo: { + bar: 'baz', + deeper: { + foo: 'bar' + } + } + } + expect(trace[0].meta_struct).to.deep.equal(expectedMetaStruct) + }) + + it('should encode meta_struct ignoring circular references in arrays', () => { + const circular = [{ + bar: 'baz' + }] + circular.push(circular) + const metaStruct = { + foo: circular + } + data[0].meta_struct = metaStruct + + encoder.encode(data) + + const buffer = encoder.makePayload() + + const decoded = msgpack.decode(buffer, { codec }) + const trace = decoded[0] + + const expectedMetaStruct = { + foo: [{ + bar: 'baz' + }] + } + expect(trace[0].meta_struct).to.deep.equal(expectedMetaStruct) + }) + + it('should encode meta_struct ignoring undefined properties', () => { + const metaStruct = { + foo: 'bar', + undefinedProperty: undefined + } + data[0].meta_struct = metaStruct + + encoder.encode(data) + + const buffer = encoder.makePayload() + + const decoded = msgpack.decode(buffer, { codec }) + const trace = decoded[0] + + const expectedMetaStruct = { + foo: 'bar' + } + expect(trace[0].meta_struct).to.deep.equal(expectedMetaStruct) + }) + + it('should encode meta_struct ignoring null properties', () => { + const metaStruct = { + foo: 'bar', + nullProperty: null + } + data[0].meta_struct = metaStruct + + encoder.encode(data) + + const buffer = encoder.makePayload() + + const decoded = msgpack.decode(buffer, { codec }) + const trace = decoded[0] + + const expectedMetaStruct = { + foo: 'bar' + } + expect(trace[0].meta_struct).to.deep.equal(expectedMetaStruct) + }) + + it('should not encode null meta_struct', () => { + data[0].meta_struct = null + + encoder.encode(data) + + const buffer = encoder.makePayload() + + const decoded = msgpack.decode(buffer, { codec }) + const trace = decoded[0] + + expect(trace[0].meta_struct).to.be.undefined + }) + }) }) diff --git a/packages/dd-trace/test/encode/0.5.spec.js b/packages/dd-trace/test/encode/0.5.spec.js index 94a8efbbb5c..2ef925c60c3 100644 --- a/packages/dd-trace/test/encode/0.5.spec.js +++ b/packages/dd-trace/test/encode/0.5.spec.js @@ -189,4 +189,31 @@ describe('encode 0.5', () => { expect(payload[5]).to.equal(1) expect(payload[11]).to.equal(0) }) + + it('should ignore meta_struct property', () => { + data[0].meta_struct = { foo: 'bar' } + + encoder.encode(data) + + const buffer = encoder.makePayload() + const decoded = msgpack.decode(buffer, { codec }) + const stringMap = decoded[0] + const trace = decoded[1][0] + + expect(trace).to.be.instanceof(Array) + expect(trace[0]).to.be.instanceof(Array) + expect(stringMap[trace[0][0]]).to.equal(data[0].service) + expect(stringMap[trace[0][1]]).to.equal(data[0].name) + expect(stringMap[trace[0][2]]).to.equal(data[0].resource) + expect(trace[0][3].toString(16)).to.equal(data[0].trace_id.toString()) + expect(trace[0][4].toString(16)).to.equal(data[0].span_id.toString()) + expect(trace[0][5].toString(16)).to.equal(data[0].parent_id.toString()) + expect(trace[0][6].toNumber()).to.equal(data[0].start) + expect(trace[0][7].toNumber()).to.equal(data[0].duration) + expect(trace[0][8]).to.equal(0) + expect(trace[0][9]).to.deep.equal({ [stringMap.indexOf('bar')]: stringMap.indexOf('baz') }) + expect(trace[0][10]).to.deep.equal({ [stringMap.indexOf('example')]: 1 }) + expect(stringMap[trace[0][11]]).to.equal('') // unset + expect(trace[0][12]).to.be.undefined // Everything works the same as without meta_struct, and nothing else is added + }) })