dd-trace 5.100.0 → 5.102.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +14 -0
- package/package.json +11 -9
- package/packages/datadog-instrumentations/src/aerospike.js +2 -2
- package/packages/datadog-instrumentations/src/ai.js +8 -8
- package/packages/datadog-instrumentations/src/amqplib.js +6 -7
- package/packages/datadog-instrumentations/src/anthropic.js +10 -10
- package/packages/datadog-instrumentations/src/apollo-server-core.js +3 -3
- package/packages/datadog-instrumentations/src/apollo-server.js +5 -5
- package/packages/datadog-instrumentations/src/avsc.js +6 -6
- package/packages/datadog-instrumentations/src/aws-sdk.js +151 -67
- package/packages/datadog-instrumentations/src/azure-durable-functions.js +8 -8
- package/packages/datadog-instrumentations/src/bluebird.js +2 -2
- package/packages/datadog-instrumentations/src/body-parser.js +2 -2
- package/packages/datadog-instrumentations/src/cassandra-driver.js +7 -7
- package/packages/datadog-instrumentations/src/child_process.js +12 -12
- package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +9 -9
- package/packages/datadog-instrumentations/src/connect.js +7 -7
- package/packages/datadog-instrumentations/src/cookie-parser.js +4 -4
- package/packages/datadog-instrumentations/src/cookie.js +2 -2
- package/packages/datadog-instrumentations/src/couchbase.js +16 -30
- package/packages/datadog-instrumentations/src/crypto.js +4 -4
- package/packages/datadog-instrumentations/src/cucumber.js +77 -16
- package/packages/datadog-instrumentations/src/cypress.js +5 -3
- package/packages/datadog-instrumentations/src/dns.js +0 -3
- package/packages/datadog-instrumentations/src/elasticsearch.js +8 -11
- package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +6 -6
- package/packages/datadog-instrumentations/src/express-session.js +4 -4
- package/packages/datadog-instrumentations/src/express.js +10 -11
- package/packages/datadog-instrumentations/src/fastify.js +2 -2
- package/packages/datadog-instrumentations/src/fs.js +14 -14
- package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +5 -7
- package/packages/datadog-instrumentations/src/google-genai.js +4 -4
- package/packages/datadog-instrumentations/src/grpc/server.js +2 -2
- package/packages/datadog-instrumentations/src/hapi.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +8 -8
- package/packages/datadog-instrumentations/src/helpers/promise.js +2 -2
- package/packages/datadog-instrumentations/src/hono.js +2 -2
- package/packages/datadog-instrumentations/src/http/client.js +26 -9
- package/packages/datadog-instrumentations/src/http/server.js +9 -9
- package/packages/datadog-instrumentations/src/jest.js +93 -63
- package/packages/datadog-instrumentations/src/kafkajs.js +9 -9
- package/packages/datadog-instrumentations/src/knex.js +17 -17
- package/packages/datadog-instrumentations/src/koa.js +12 -12
- package/packages/datadog-instrumentations/src/ldapjs.js +5 -5
- package/packages/datadog-instrumentations/src/light-my-request.js +2 -2
- package/packages/datadog-instrumentations/src/limitd-client.js +4 -4
- package/packages/datadog-instrumentations/src/lodash.js +4 -4
- package/packages/datadog-instrumentations/src/mariadb.js +13 -13
- package/packages/datadog-instrumentations/src/memcached.js +2 -2
- package/packages/datadog-instrumentations/src/microgateway-core.js +2 -2
- package/packages/datadog-instrumentations/src/mocha/common.js +7 -4
- package/packages/datadog-instrumentations/src/mocha/main.js +37 -14
- package/packages/datadog-instrumentations/src/mocha/utils.js +133 -16
- package/packages/datadog-instrumentations/src/mocha/worker.js +12 -7
- package/packages/datadog-instrumentations/src/mongodb-core.js +9 -22
- package/packages/datadog-instrumentations/src/mongodb.js +5 -5
- package/packages/datadog-instrumentations/src/mongoose.js +21 -21
- package/packages/datadog-instrumentations/src/mquery.js +5 -5
- package/packages/datadog-instrumentations/src/multer.js +4 -4
- package/packages/datadog-instrumentations/src/mysql.js +16 -16
- package/packages/datadog-instrumentations/src/mysql2.js +4 -4
- package/packages/datadog-instrumentations/src/net.js +14 -8
- package/packages/datadog-instrumentations/src/nyc.js +5 -5
- package/packages/datadog-instrumentations/src/openai.js +19 -19
- package/packages/datadog-instrumentations/src/oracledb.js +6 -6
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +11 -6
- package/packages/datadog-instrumentations/src/passport-utils.js +5 -5
- package/packages/datadog-instrumentations/src/pg.js +15 -15
- package/packages/datadog-instrumentations/src/pino.js +6 -10
- package/packages/datadog-instrumentations/src/playwright.js +20 -15
- package/packages/datadog-instrumentations/src/protobufjs.js +16 -16
- package/packages/datadog-instrumentations/src/redis.js +1 -2
- package/packages/datadog-instrumentations/src/restify.js +2 -2
- package/packages/datadog-instrumentations/src/router.js +12 -12
- package/packages/datadog-instrumentations/src/stripe.js +12 -12
- package/packages/datadog-instrumentations/src/vitest.js +107 -26
- package/packages/datadog-instrumentations/src/winston.js +4 -4
- package/packages/datadog-instrumentations/src/ws.js +7 -7
- package/packages/datadog-plugin-aws-sdk/src/base.js +52 -4
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +19 -12
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +45 -35
- package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +33 -22
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +12 -13
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +73 -54
- package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +19 -17
- package/packages/datadog-plugin-aws-sdk/src/util.js +22 -0
- package/packages/datadog-plugin-bullmq/src/consumer.js +2 -2
- package/packages/datadog-plugin-bullmq/src/producer.js +14 -20
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -6
- package/packages/datadog-plugin-cucumber/src/index.js +4 -0
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +18 -4
- package/packages/datadog-plugin-cypress/src/plugin.js +5 -14
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +1 -5
- package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +3 -1
- package/packages/datadog-plugin-http/src/client.js +1 -5
- package/packages/datadog-plugin-jest/src/util.js +1 -2
- package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -9
- package/packages/datadog-plugin-kafkajs/src/producer.js +2 -8
- package/packages/datadog-plugin-mocha/src/index.js +4 -0
- package/packages/datadog-plugin-mongodb-core/src/index.js +2 -1
- package/packages/datadog-plugin-openai/src/tracing.js +12 -23
- package/packages/datadog-plugin-playwright/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +8 -1
- package/packages/datadog-shimmer/src/shimmer.js +7 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +81 -81
- package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +2 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +2 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +2 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +1 -3
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +83 -48
- package/packages/dd-trace/src/appsec/index.js +21 -24
- package/packages/dd-trace/src/appsec/reporter.js +7 -2
- package/packages/dd-trace/src/appsec/rule_manager.js +4 -2
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +31 -16
- package/packages/dd-trace/src/ci-visibility/lage.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -33
- package/packages/dd-trace/src/config/config-types.d.ts +0 -2
- package/packages/dd-trace/src/config/git_properties.js +2 -2
- package/packages/dd-trace/src/config/index.js +1 -55
- package/packages/dd-trace/src/datastreams/checkpointer.js +4 -10
- package/packages/dd-trace/src/datastreams/encoding.js +39 -28
- package/packages/dd-trace/src/datastreams/index.js +2 -1
- package/packages/dd-trace/src/datastreams/pathway.js +29 -26
- package/packages/dd-trace/src/datastreams/processor.js +18 -17
- package/packages/dd-trace/src/datastreams/size.js +6 -2
- package/packages/dd-trace/src/debugger/config.js +5 -2
- package/packages/dd-trace/src/debugger/devtools_client/index.js +2 -5
- package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot-pruner.js +1 -0
- package/packages/dd-trace/src/dogstatsd.js +10 -7
- package/packages/dd-trace/src/encode/0.4.js +759 -234
- package/packages/dd-trace/src/encode/0.5.js +15 -9
- package/packages/dd-trace/src/encode/agentless-json.js +2 -2
- package/packages/dd-trace/src/encode/tags-processors.js +2 -27
- package/packages/dd-trace/src/exporters/common/request.js +22 -11
- package/packages/dd-trace/src/exporters/common/retry.js +104 -0
- package/packages/dd-trace/src/git_metadata.js +66 -0
- package/packages/dd-trace/src/git_metadata_tagger.js +13 -5
- package/packages/dd-trace/src/id.js +15 -26
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -2
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +27 -16
- package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +3 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +33 -13
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +20 -50
- package/packages/dd-trace/src/llmobs/sdk.js +29 -27
- package/packages/dd-trace/src/llmobs/span_processor.js +52 -6
- package/packages/dd-trace/src/llmobs/tagger.js +42 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +29 -0
- package/packages/dd-trace/src/llmobs/util.js +81 -5
- package/packages/dd-trace/src/msgpack/chunk.js +6 -3
- package/packages/dd-trace/src/openfeature/noop.js +40 -36
- package/packages/dd-trace/src/openfeature/writers/exposures.js +33 -52
- package/packages/dd-trace/src/opentelemetry/active-span-proxy.js +42 -0
- package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +106 -0
- package/packages/dd-trace/src/opentelemetry/context_manager.js +11 -2
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +1 -2
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +188 -50
- package/packages/dd-trace/src/opentelemetry/span.js +42 -80
- package/packages/dd-trace/src/opentelemetry/tracer.js +0 -22
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +65 -27
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +2 -11
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +58 -22
- package/packages/dd-trace/src/opentracing/span.js +56 -48
- package/packages/dd-trace/src/opentracing/span_context.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +1 -1
- package/packages/dd-trace/src/plugins/util/git-cache.js +3 -5
- package/packages/dd-trace/src/plugins/util/test.js +19 -7
- package/packages/dd-trace/src/plugins/util/url.js +1 -3
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
- package/packages/dd-trace/src/plugins/util/web.js +5 -7
- package/packages/dd-trace/src/priority_sampler.js +6 -4
- package/packages/dd-trace/src/profiling/config.js +5 -4
- package/packages/dd-trace/src/profiling/profilers/events.js +3 -23
- package/packages/dd-trace/src/profiling/profilers/wall.js +4 -5
- package/packages/dd-trace/src/remote_config/index.js +5 -3
- package/packages/dd-trace/src/runtime_metrics/index.js +2 -2
- package/packages/dd-trace/src/scope.js +3 -10
- package/packages/dd-trace/src/serverless.js +1 -4
- package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +7 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +4 -0
- package/packages/dd-trace/src/span_format.js +52 -5
- package/packages/dd-trace/src/span_processor.js +0 -4
- package/packages/dd-trace/src/spanleak.js +0 -1
- package/packages/dd-trace/src/tracer.js +7 -7
- package/packages/dd-trace/src/util.js +17 -0
|
@@ -3,34 +3,226 @@
|
|
|
3
3
|
const getConfig = require('../config')
|
|
4
4
|
const { MsgpackChunk, MsgpackEncoder } = require('../msgpack')
|
|
5
5
|
const log = require('../log')
|
|
6
|
-
const {
|
|
6
|
+
const { normalizeSpan } = require('./tags-processors')
|
|
7
7
|
|
|
8
8
|
const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// Pre-encoded static keys + value-prefix bytes; the hot encode loop emits
|
|
11
|
+
// each via one Uint8Array.set instead of routing through the string cache.
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} key fixstr key, must be < 32 UTF-8 bytes.
|
|
15
|
+
* @returns {Buffer}
|
|
16
|
+
*/
|
|
17
|
+
function buildKey (key) {
|
|
18
|
+
const length = Buffer.byteLength(key)
|
|
19
|
+
const buffer = Buffer.allocUnsafe(length + 1)
|
|
20
|
+
buffer[0] = length | 0xA0
|
|
21
|
+
buffer.utf8Write(key, 1, length)
|
|
22
|
+
return buffer
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} key fixstr key, must be < 32 UTF-8 bytes.
|
|
27
|
+
* @param {number} prefix msgpack prefix byte for the value that follows the key.
|
|
28
|
+
* @returns {Buffer}
|
|
29
|
+
*/
|
|
30
|
+
function buildKeyWithPrefix (key, prefix) {
|
|
31
|
+
const length = Buffer.byteLength(key)
|
|
32
|
+
const buffer = Buffer.allocUnsafe(length + 2)
|
|
33
|
+
buffer[0] = length | 0xA0
|
|
34
|
+
buffer.utf8Write(key, 1, length)
|
|
35
|
+
buffer[length + 1] = prefix
|
|
36
|
+
return buffer
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const KEY_TYPE = buildKey('type')
|
|
40
|
+
const KEY_NAME = buildKey('name')
|
|
41
|
+
const KEY_RESOURCE = buildKey('resource')
|
|
42
|
+
const KEY_SERVICE = buildKey('service')
|
|
43
|
+
const KEY_SPAN_EVENTS = buildKey('span_events')
|
|
44
|
+
const KEY_META_STRUCT = buildKey('meta_struct')
|
|
45
|
+
const KEY_TRACE_ID_PREFIX = buildKeyWithPrefix('trace_id', 0xCF)
|
|
46
|
+
const KEY_SPAN_ID_PREFIX = buildKeyWithPrefix('span_id', 0xCF)
|
|
47
|
+
const KEY_PARENT_ID_PREFIX = buildKeyWithPrefix('parent_id', 0xCF)
|
|
48
|
+
const KEY_ERROR_PREFIX = buildKeyWithPrefix('error', 0xCE)
|
|
49
|
+
const KEY_START_PREFIX = buildKeyWithPrefix('start', 0xCF)
|
|
50
|
+
const KEY_DURATION_PREFIX = buildKeyWithPrefix('duration', 0xCF)
|
|
51
|
+
const KEY_META_PREFIX = buildKeyWithPrefix('meta', 0xDF)
|
|
52
|
+
const KEY_METRICS_PREFIX = buildKeyWithPrefix('metrics', 0xDF)
|
|
53
|
+
|
|
54
|
+
// Span-event field keys — `name` is shared with the span-level KEY_NAME.
|
|
55
|
+
const KEY_EVENT_TIME = buildKey('time_unix_nano')
|
|
56
|
+
const KEY_EVENT_ATTRIBUTES = buildKey('attributes')
|
|
57
|
+
|
|
58
|
+
// Pre-encoded prefix for a span-event-attribute typed wrapper:
|
|
59
|
+
// `[0x82 fixmap(2), 'type' fixstr, type fixint, '<value>_value' fixstr]`.
|
|
60
|
+
// The hot path emits one of these constants + the raw value, so the encoder
|
|
61
|
+
// never has to allocate `{ type: N, *_value: ... }` wrappers.
|
|
62
|
+
function buildAttrPrefix (typeByte, valueKey) {
|
|
63
|
+
const valueKeyBuf = buildKey(valueKey)
|
|
64
|
+
const buf = Buffer.allocUnsafe(7 + valueKeyBuf.length)
|
|
65
|
+
buf[0] = 0x82
|
|
66
|
+
buf[1] = 0xA4
|
|
67
|
+
buf[2] = 0x74 // t
|
|
68
|
+
buf[3] = 0x79 // y
|
|
69
|
+
buf[4] = 0x70 // p
|
|
70
|
+
buf[5] = 0x65 // e
|
|
71
|
+
buf[6] = typeByte
|
|
72
|
+
valueKeyBuf.copy(buf, 7)
|
|
73
|
+
return buf
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const ATTR_PREFIX_STRING = buildAttrPrefix(0x00, 'string_value')
|
|
77
|
+
const ATTR_PREFIX_BOOL = buildAttrPrefix(0x01, 'bool_value')
|
|
78
|
+
const ATTR_PREFIX_INT = buildAttrPrefix(0x02, 'int_value')
|
|
79
|
+
const ATTR_PREFIX_DOUBLE = buildAttrPrefix(0x03, 'double_value')
|
|
80
|
+
|
|
81
|
+
// Outer array attribute is the only nested case: `[0x82, 'type', 4,
|
|
82
|
+
// 'array_value', 0x81 fixmap(1), 'values', 0xDD array32-prefix]`. The 4-byte
|
|
83
|
+
// length slot follows.
|
|
84
|
+
const ATTR_PREFIX_ARRAY = Buffer.concat([
|
|
85
|
+
buildAttrPrefix(0x04, 'array_value'),
|
|
86
|
+
Buffer.from([0x81]),
|
|
87
|
+
buildKey('values'),
|
|
88
|
+
Buffer.from([0xDD]),
|
|
89
|
+
])
|
|
90
|
+
|
|
91
|
+
// Pre-encoded boolean payloads: `[ATTR_PREFIX_BOOL, 0xC3 / 0xC2]`. Used by
|
|
92
|
+
// `#emitAttribute` and `#emitArrayItem` to emit the whole bool wrapper in a
|
|
93
|
+
// single `bytes.set`.
|
|
94
|
+
const ATTR_PAYLOAD_BOOL_TRUE = Buffer.concat([ATTR_PREFIX_BOOL, Buffer.from([0xC3])])
|
|
95
|
+
const ATTR_PAYLOAD_BOOL_FALSE = Buffer.concat([ATTR_PREFIX_BOOL, Buffer.from([0xC2])])
|
|
96
|
+
|
|
97
|
+
function formatSpanWithLegacyEvents (span) {
|
|
98
|
+
span = normalizeSpan(span)
|
|
12
99
|
if (span.span_events) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
100
|
+
span.meta.events = stringifySpanEvents(span.span_events)
|
|
101
|
+
// `= undefined` over `delete` to keep the span's hidden class — `delete`
|
|
102
|
+
// would push every event-bearing span into V8 dictionary mode.
|
|
103
|
+
span.span_events = undefined
|
|
104
|
+
}
|
|
105
|
+
return span
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Hand-written stringifier for `span.span_events`. The shape is fixed by
|
|
110
|
+
* `extractSpanEvents` (`{ name, time_unix_nano, attributes? }`) and attribute
|
|
111
|
+
* values are pre-sanitized to primitives or arrays of primitives, so we can
|
|
112
|
+
* skip everything `JSON.stringify` does for the generic case (toJSON probing,
|
|
113
|
+
* key iteration over the prototype chain, replacer hooks). Output matches
|
|
114
|
+
* `JSON.stringify(spanEvents)` byte-for-byte for the post-sanitization shape.
|
|
115
|
+
*
|
|
116
|
+
* @param {Array<{ name: string, time_unix_nano: number, attributes?: object }>} spanEvents
|
|
117
|
+
* @returns {string}
|
|
118
|
+
*/
|
|
119
|
+
function stringifySpanEvents (spanEvents) {
|
|
120
|
+
let result = '['
|
|
121
|
+
for (let index = 0; index < spanEvents.length; index++) {
|
|
122
|
+
if (index > 0) result += ','
|
|
123
|
+
const event = spanEvents[index]
|
|
124
|
+
// `addEvent` does not type-check `name`; defer the unusual cases to
|
|
125
|
+
// `JSON.stringify` so non-string names match the prior behaviour
|
|
126
|
+
// instead of throwing in `escapeJsonString`.
|
|
127
|
+
if (typeof event.name !== 'string') {
|
|
128
|
+
result += JSON.stringify(event)
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
result += '{"name":' + escapeJsonString(event.name) +
|
|
132
|
+
',"time_unix_nano":' + jsonNumber(event.time_unix_nano)
|
|
133
|
+
if (event.attributes) {
|
|
134
|
+
result += ',"attributes":' + stringifyAttributes(event.attributes)
|
|
135
|
+
}
|
|
136
|
+
result += '}'
|
|
137
|
+
}
|
|
138
|
+
return result + ']'
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function stringifyAttributes (attributes) {
|
|
142
|
+
let result = '{'
|
|
143
|
+
let first = true
|
|
144
|
+
for (const key of Object.keys(attributes)) {
|
|
145
|
+
if (first) {
|
|
146
|
+
first = false
|
|
16
147
|
} else {
|
|
17
|
-
|
|
18
|
-
delete span.span_events
|
|
148
|
+
result += ','
|
|
19
149
|
}
|
|
150
|
+
result += escapeJsonString(key) + ':' + stringifyAttributeValue(attributes[key])
|
|
20
151
|
}
|
|
21
|
-
return
|
|
152
|
+
return result + '}'
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function stringifyAttributeValue (value) {
|
|
156
|
+
if (typeof value === 'string') return escapeJsonString(value)
|
|
157
|
+
if (typeof value === 'number') return jsonNumber(value)
|
|
158
|
+
if (typeof value === 'boolean') return value ? 'true' : 'false'
|
|
159
|
+
if (Array.isArray(value)) {
|
|
160
|
+
let result = '['
|
|
161
|
+
for (let index = 0; index < value.length; index++) {
|
|
162
|
+
if (index > 0) result += ','
|
|
163
|
+
result += stringifyAttributeValue(value[index])
|
|
164
|
+
}
|
|
165
|
+
return result + ']'
|
|
166
|
+
}
|
|
167
|
+
// Sanitization rejects everything else, but keep the safety net.
|
|
168
|
+
return 'null'
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Match `JSON.stringify` for numbers: `NaN` and `±Infinity` collapse to the
|
|
173
|
+
* literal `null`, everything else uses ECMAScript's default `Number → String`
|
|
174
|
+
* conversion (which is what `JSON.stringify` calls internally).
|
|
175
|
+
*
|
|
176
|
+
* @param {number} value
|
|
177
|
+
* @returns {string}
|
|
178
|
+
*/
|
|
179
|
+
function jsonNumber (value) {
|
|
180
|
+
if (Number.isFinite(value)) return String(value)
|
|
181
|
+
return 'null'
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Fast path: scan once, and if no character in the string requires JSON
|
|
186
|
+
* escaping, emit `"<str>"` as-is. The scanned chars are `"`, `\`, and any
|
|
187
|
+
* control char in the U+0000–U+001F range. Anything else delegates to
|
|
188
|
+
* `JSON.stringify` for full spec-compliant escaping (surrogate pairs,
|
|
189
|
+
* lone surrogates, etc.).
|
|
190
|
+
*
|
|
191
|
+
* @param {string} value
|
|
192
|
+
* @returns {string}
|
|
193
|
+
*/
|
|
194
|
+
function escapeJsonString (value) {
|
|
195
|
+
for (let index = 0; index < value.length; index++) {
|
|
196
|
+
const code = value.charCodeAt(index)
|
|
197
|
+
if (code < 0x20 || code === 0x22 || code === 0x5C) {
|
|
198
|
+
return JSON.stringify(value)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return '"' + value + '"'
|
|
22
202
|
}
|
|
23
203
|
|
|
24
204
|
class AgentEncoder {
|
|
205
|
+
#msgpack = new MsgpackEncoder()
|
|
206
|
+
#limit
|
|
207
|
+
#writer
|
|
208
|
+
#config
|
|
209
|
+
#debugEncoding
|
|
210
|
+
#formatSpan
|
|
211
|
+
|
|
25
212
|
constructor (writer, limit = SOFT_LIMIT) {
|
|
26
|
-
this
|
|
27
|
-
this._limit = limit
|
|
213
|
+
this.#limit = limit
|
|
28
214
|
this._traceBytes = new MsgpackChunk()
|
|
29
215
|
this._stringBytes = new MsgpackChunk()
|
|
30
|
-
this
|
|
216
|
+
this.#writer = writer
|
|
31
217
|
this._reset()
|
|
32
|
-
this
|
|
33
|
-
this
|
|
218
|
+
this.#config = getConfig()
|
|
219
|
+
this.#debugEncoding = this.#config.DD_TRACE_ENCODING_DEBUG
|
|
220
|
+
// Pick the per-span formatter once so the hot loop pays no per-span
|
|
221
|
+
// config check. The native path doesn't need to reshape `span_events`
|
|
222
|
+
// because `#encodeSpanEvents` works directly on the raw attributes.
|
|
223
|
+
this.#formatSpan = this.#config.DD_TRACE_NATIVE_SPAN_EVENTS
|
|
224
|
+
? normalizeSpan
|
|
225
|
+
: formatSpanWithLegacyEvents
|
|
34
226
|
}
|
|
35
227
|
|
|
36
228
|
count () {
|
|
@@ -45,21 +237,19 @@ class AgentEncoder {
|
|
|
45
237
|
|
|
46
238
|
this._encode(bytes, trace)
|
|
47
239
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (this._debugEncoding) {
|
|
240
|
+
if (this.#debugEncoding) {
|
|
241
|
+
const end = bytes.length
|
|
51
242
|
// eslint-disable-next-line eslint-rules/eslint-log-printf-style
|
|
52
243
|
log.debug(() => {
|
|
53
244
|
const hex = bytes.buffer.subarray(start, end).toString('hex').match(/../g).join(' ')
|
|
54
|
-
|
|
55
245
|
return `Adding encoded trace to buffer: ${hex}`
|
|
56
246
|
})
|
|
57
247
|
}
|
|
58
248
|
|
|
59
|
-
//
|
|
60
|
-
if (this._traceBytes.length > this
|
|
249
|
+
// Soft limit overshoot is fine — the agent caps at 50 MB.
|
|
250
|
+
if (this._traceBytes.length > this.#limit || this._stringBytes.length > this.#limit) {
|
|
61
251
|
log.debug('Buffer went over soft limit, flushing')
|
|
62
|
-
this.
|
|
252
|
+
this.#writer.flush()
|
|
63
253
|
}
|
|
64
254
|
}
|
|
65
255
|
|
|
@@ -81,56 +271,118 @@ class AgentEncoder {
|
|
|
81
271
|
_encode (bytes, trace) {
|
|
82
272
|
this._encodeArrayPrefix(bytes, trace)
|
|
83
273
|
|
|
274
|
+
const formatSpan = this.#formatSpan
|
|
275
|
+
const stringMap = this._stringMap
|
|
276
|
+
// Snapshot the string buffer so we can detect a mid-encode resize and
|
|
277
|
+
// release the old backing store afterwards (see `#refreshStringCache`).
|
|
278
|
+
const stringBufferAtStart = this._stringBytes.buffer
|
|
279
|
+
|
|
84
280
|
for (let span of trace) {
|
|
85
|
-
span = formatSpan(span
|
|
86
|
-
bytes.reserve(1)
|
|
281
|
+
span = formatSpan(span)
|
|
87
282
|
|
|
88
|
-
// this is the original size of the fixed map for span attributes that always exist
|
|
89
283
|
let mapSize = 11
|
|
284
|
+
if (span.type) mapSize++
|
|
285
|
+
if (span.meta_struct) mapSize++
|
|
286
|
+
if (span.span_events) mapSize++
|
|
287
|
+
|
|
288
|
+
// Pre-fetch the cached string entries up front and fuse the map prefix,
|
|
289
|
+
// optional `type`, three IDs, and `name` / `resource` / `service`
|
|
290
|
+
// emissions into a single `bytes.reserve` + sequential native writes.
|
|
291
|
+
// Replaces seven `bytes.reserve` calls per span (one each for the
|
|
292
|
+
// header, type, three IDs, three strings) with one.
|
|
293
|
+
let typeEntry
|
|
294
|
+
if (span.type) {
|
|
295
|
+
typeEntry = stringMap[span.type] ?? this._cacheString(span.type)
|
|
296
|
+
}
|
|
297
|
+
const nameEntry = stringMap[span.name] ?? this._cacheString(span.name)
|
|
298
|
+
const resourceEntry = stringMap[span.resource] ?? this._cacheString(span.resource)
|
|
299
|
+
const serviceEntry = stringMap[span.service] ?? this._cacheString(span.service)
|
|
300
|
+
const nameLen = nameEntry.length
|
|
301
|
+
const resourceLen = resourceEntry.length
|
|
302
|
+
const serviceLen = serviceEntry.length
|
|
303
|
+
|
|
304
|
+
// 1 byte map prefix + 3 ID fields (10/9/11 bytes prefix + 8 bytes value
|
|
305
|
+
// each) + the three string fields.
|
|
306
|
+
let blockSize = 1 +
|
|
307
|
+
KEY_TRACE_ID_PREFIX.length + 8 +
|
|
308
|
+
KEY_SPAN_ID_PREFIX.length + 8 +
|
|
309
|
+
KEY_PARENT_ID_PREFIX.length + 8 +
|
|
310
|
+
KEY_NAME.length + nameLen +
|
|
311
|
+
KEY_RESOURCE.length + resourceLen +
|
|
312
|
+
KEY_SERVICE.length + serviceLen
|
|
313
|
+
if (typeEntry) blockSize += KEY_TYPE.length + typeEntry.length
|
|
314
|
+
|
|
315
|
+
const blockOffset = bytes.length
|
|
316
|
+
bytes.reserve(blockSize)
|
|
317
|
+
const target = bytes.buffer
|
|
318
|
+
let cursor = blockOffset
|
|
319
|
+
|
|
320
|
+
target[cursor++] = 0x80 + mapSize
|
|
321
|
+
|
|
322
|
+
if (typeEntry) {
|
|
323
|
+
target.set(KEY_TYPE, cursor)
|
|
324
|
+
cursor += KEY_TYPE.length
|
|
325
|
+
target.set(typeEntry, cursor)
|
|
326
|
+
cursor += typeEntry.length
|
|
327
|
+
}
|
|
90
328
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (span.span_events) mapSize += 1
|
|
329
|
+
cursor = this.#writeIdAt(target, cursor, KEY_TRACE_ID_PREFIX, span.trace_id)
|
|
330
|
+
cursor = this.#writeIdAt(target, cursor, KEY_SPAN_ID_PREFIX, span.span_id)
|
|
331
|
+
cursor = this.#writeIdAt(target, cursor, KEY_PARENT_ID_PREFIX, span.parent_id)
|
|
95
332
|
|
|
96
|
-
|
|
333
|
+
target.set(KEY_NAME, cursor)
|
|
334
|
+
cursor += KEY_NAME.length
|
|
335
|
+
target.set(nameEntry, cursor)
|
|
336
|
+
cursor += nameLen
|
|
97
337
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
338
|
+
target.set(KEY_RESOURCE, cursor)
|
|
339
|
+
cursor += KEY_RESOURCE.length
|
|
340
|
+
target.set(resourceEntry, cursor)
|
|
341
|
+
cursor += resourceLen
|
|
342
|
+
|
|
343
|
+
target.set(KEY_SERVICE, cursor)
|
|
344
|
+
cursor += KEY_SERVICE.length
|
|
345
|
+
target.set(serviceEntry, cursor)
|
|
346
|
+
|
|
347
|
+
this.#writeIntegerField(bytes, KEY_ERROR_PREFIX, span.error)
|
|
348
|
+
this.#writeLongField(bytes, KEY_START_PREFIX, span.start)
|
|
349
|
+
this.#writeLongField(bytes, KEY_DURATION_PREFIX, span.duration)
|
|
350
|
+
|
|
351
|
+
this.#encodeMetaEntries(bytes, KEY_META_PREFIX, span.meta)
|
|
352
|
+
this.#encodeMetaEntries(bytes, KEY_METRICS_PREFIX, span.metrics)
|
|
102
353
|
|
|
103
|
-
this._encodeString(bytes, 'trace_id')
|
|
104
|
-
this._encodeId(bytes, span.trace_id)
|
|
105
|
-
this._encodeString(bytes, 'span_id')
|
|
106
|
-
this._encodeId(bytes, span.span_id)
|
|
107
|
-
this._encodeString(bytes, 'parent_id')
|
|
108
|
-
this._encodeId(bytes, span.parent_id)
|
|
109
|
-
this._encodeString(bytes, 'name')
|
|
110
|
-
this._encodeString(bytes, span.name)
|
|
111
|
-
this._encodeString(bytes, 'resource')
|
|
112
|
-
this._encodeString(bytes, span.resource)
|
|
113
|
-
this._encodeString(bytes, 'service')
|
|
114
|
-
this._encodeString(bytes, span.service)
|
|
115
|
-
this._encodeString(bytes, 'error')
|
|
116
|
-
this._encodeInteger(bytes, span.error)
|
|
117
|
-
this._encodeString(bytes, 'start')
|
|
118
|
-
this._encodeLong(bytes, span.start)
|
|
119
|
-
this._encodeString(bytes, 'duration')
|
|
120
|
-
this._encodeLong(bytes, span.duration)
|
|
121
|
-
this._encodeString(bytes, 'meta')
|
|
122
|
-
this._encodeMap(bytes, span.meta)
|
|
123
|
-
this._encodeString(bytes, 'metrics')
|
|
124
|
-
this._encodeMap(bytes, span.metrics)
|
|
125
354
|
if (span.span_events) {
|
|
126
|
-
|
|
127
|
-
this
|
|
355
|
+
bytes.set(KEY_SPAN_EVENTS)
|
|
356
|
+
this.#encodeSpanEvents(bytes, span.span_events)
|
|
128
357
|
}
|
|
129
358
|
if (span.meta_struct) {
|
|
130
|
-
|
|
131
|
-
this
|
|
359
|
+
bytes.set(KEY_META_STRUCT)
|
|
360
|
+
this.#encodeMetaStruct(bytes, span.meta_struct)
|
|
132
361
|
}
|
|
133
362
|
}
|
|
363
|
+
|
|
364
|
+
if (this._stringBytes.buffer !== stringBufferAtStart) {
|
|
365
|
+
this.#refreshStringCache()
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Rebuild the cached string subarrays in `_stringMap` against the current
|
|
371
|
+
* `_stringBytes.buffer`. After `MsgpackChunk.reserve()` resizes, the prior
|
|
372
|
+
* subarrays still reference the old `Buffer`'s `ArrayBuffer` and pin it
|
|
373
|
+
* until `_reset()` clears the map; for a payload that grows 2 → 4 → 8 MB
|
|
374
|
+
* that is up to 6 MB of avoidable peak memory. `Buffer.allocUnsafe(>= 2
|
|
375
|
+
* MB)` bypasses the small-allocation pool and starts at offset 0 in its
|
|
376
|
+
* `ArrayBuffer`, so the old subarray's `byteOffset` is the entry's start
|
|
377
|
+
* position in the new buffer.
|
|
378
|
+
*/
|
|
379
|
+
#refreshStringCache () {
|
|
380
|
+
const newBuffer = this._stringBytes.buffer
|
|
381
|
+
const stringMap = this._stringMap
|
|
382
|
+
for (const key of Object.keys(stringMap)) {
|
|
383
|
+
const old = stringMap[key]
|
|
384
|
+
stringMap[key] = newBuffer.subarray(old.byteOffset, old.byteOffset + old.length)
|
|
385
|
+
}
|
|
134
386
|
}
|
|
135
387
|
|
|
136
388
|
_reset () {
|
|
@@ -144,123 +396,302 @@ class AgentEncoder {
|
|
|
144
396
|
}
|
|
145
397
|
|
|
146
398
|
_encodeBuffer (bytes, buffer) {
|
|
147
|
-
this.
|
|
399
|
+
this.#msgpack.encodeBin(bytes, buffer)
|
|
148
400
|
}
|
|
149
401
|
|
|
150
402
|
_encodeBool (bytes, value) {
|
|
151
|
-
this.
|
|
403
|
+
this.#msgpack.encodeBoolean(bytes, value)
|
|
152
404
|
}
|
|
153
405
|
|
|
154
406
|
_encodeArrayPrefix (bytes, value) {
|
|
155
|
-
this.
|
|
407
|
+
this.#msgpack.encodeArrayPrefix(bytes, value)
|
|
156
408
|
}
|
|
157
409
|
|
|
158
410
|
_encodeMapPrefix (bytes, keysLength) {
|
|
159
|
-
this.
|
|
411
|
+
this.#msgpack.encodeMapPrefix(bytes, keysLength)
|
|
160
412
|
}
|
|
161
413
|
|
|
162
414
|
_encodeByte (bytes, value) {
|
|
163
|
-
this.
|
|
415
|
+
this.#msgpack.encodeByte(bytes, value)
|
|
164
416
|
}
|
|
165
417
|
|
|
166
418
|
// TODO: Use BigInt instead.
|
|
167
|
-
_encodeId (bytes,
|
|
419
|
+
_encodeId (bytes, identifier) {
|
|
420
|
+
const idBuffer = identifier.toBuffer()
|
|
421
|
+
const start = idBuffer.length - 8
|
|
168
422
|
const offset = bytes.length
|
|
169
423
|
|
|
170
424
|
bytes.reserve(9)
|
|
171
425
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
bytes.buffer[offset + 8] = id[7]
|
|
426
|
+
const target = bytes.buffer
|
|
427
|
+
target[offset] = 0xCF
|
|
428
|
+
target[offset + 1] = idBuffer[start]
|
|
429
|
+
target[offset + 2] = idBuffer[start + 1]
|
|
430
|
+
target[offset + 3] = idBuffer[start + 2]
|
|
431
|
+
target[offset + 4] = idBuffer[start + 3]
|
|
432
|
+
target[offset + 5] = idBuffer[start + 4]
|
|
433
|
+
target[offset + 6] = idBuffer[start + 5]
|
|
434
|
+
target[offset + 7] = idBuffer[start + 6]
|
|
435
|
+
target[offset + 8] = idBuffer[start + 7]
|
|
183
436
|
}
|
|
184
437
|
|
|
185
438
|
_encodeNumber (bytes, value) {
|
|
186
|
-
this.
|
|
439
|
+
this.#msgpack.encodeNumber(bytes, value)
|
|
187
440
|
}
|
|
188
441
|
|
|
189
442
|
_encodeInteger (bytes, value) {
|
|
190
|
-
this.
|
|
443
|
+
this.#msgpack.encodeInteger(bytes, value)
|
|
191
444
|
}
|
|
192
445
|
|
|
193
446
|
_encodeLong (bytes, value) {
|
|
194
|
-
this.
|
|
447
|
+
this.#msgpack.encodeLong(bytes, value)
|
|
195
448
|
}
|
|
196
449
|
|
|
450
|
+
// Single pass: reserve the count slot, encode entries while counting, patch the count.
|
|
197
451
|
_encodeMap (bytes, value) {
|
|
198
|
-
const
|
|
199
|
-
|
|
452
|
+
const offset = bytes.length
|
|
453
|
+
bytes.reserve(5)
|
|
454
|
+
bytes.buffer[offset] = 0xDF
|
|
455
|
+
|
|
456
|
+
let count = 0
|
|
457
|
+
for (const key of Object.keys(value)) {
|
|
458
|
+
const entryValue = value[key]
|
|
459
|
+
if (typeof entryValue === 'string') {
|
|
460
|
+
this._encodeString(bytes, key)
|
|
461
|
+
this._encodeString(bytes, entryValue)
|
|
462
|
+
count++
|
|
463
|
+
} else if (typeof entryValue === 'number') {
|
|
464
|
+
this._encodeString(bytes, key)
|
|
465
|
+
this.#encodeFloat(bytes, entryValue)
|
|
466
|
+
count++
|
|
467
|
+
}
|
|
468
|
+
}
|
|
200
469
|
|
|
201
|
-
|
|
470
|
+
const target = bytes.buffer
|
|
471
|
+
target[offset + 1] = count >>> 24
|
|
472
|
+
target[offset + 2] = count >>> 16
|
|
473
|
+
target[offset + 3] = count >>> 8
|
|
474
|
+
target[offset + 4] = count
|
|
475
|
+
}
|
|
202
476
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
477
|
+
_encodeString (bytes, value = '') {
|
|
478
|
+
const entry = this._stringMap[value] ?? this._cacheString(value)
|
|
479
|
+
const length = entry.length
|
|
480
|
+
const offset = bytes.length
|
|
481
|
+
bytes.reserve(length)
|
|
482
|
+
bytes.buffer.set(entry, offset)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
_cacheString (value) {
|
|
486
|
+
let entry = this._stringMap[value]
|
|
487
|
+
if (entry === undefined) {
|
|
488
|
+
this._stringCount++
|
|
489
|
+
const start = this._stringBytes.length
|
|
490
|
+
const written = this._stringBytes.write(value)
|
|
491
|
+
entry = this._stringBytes.buffer.subarray(start, start + written)
|
|
492
|
+
this._stringMap[value] = entry
|
|
206
493
|
}
|
|
494
|
+
return entry
|
|
207
495
|
}
|
|
208
496
|
|
|
209
|
-
|
|
497
|
+
_writeArrayPrefix (buffer, offset, count) {
|
|
498
|
+
buffer[offset++] = 0xDD
|
|
499
|
+
buffer.writeUInt32BE(count, offset)
|
|
500
|
+
|
|
501
|
+
return offset + 4
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
_writeTraces (buffer, offset = 0) {
|
|
505
|
+
offset = this._writeArrayPrefix(buffer, offset, this._traceCount)
|
|
506
|
+
offset += this._traceBytes.buffer.copy(buffer, offset, 0, this._traceBytes.length)
|
|
507
|
+
|
|
508
|
+
return offset
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Fast path for `span.meta` / `span.metrics`. Inlines the string cache so
|
|
513
|
+
* each entry is one reserve (not two) and skips the polymorphic dispatch.
|
|
514
|
+
*
|
|
515
|
+
* @param {MsgpackChunk} bytes
|
|
516
|
+
* @param {Buffer} keyPrefix Precomputed `[key, 0xDF]`.
|
|
517
|
+
* @param {Record<string, unknown>} value
|
|
518
|
+
*/
|
|
519
|
+
#encodeMetaEntries (bytes, keyPrefix, value) {
|
|
520
|
+
const keyPrefixLen = keyPrefix.length
|
|
521
|
+
const headerOffset = bytes.length
|
|
522
|
+
bytes.reserve(keyPrefixLen + 4)
|
|
523
|
+
bytes.buffer.set(keyPrefix, headerOffset)
|
|
524
|
+
const countOffset = headerOffset + keyPrefixLen
|
|
525
|
+
|
|
526
|
+
const stringMap = this._stringMap
|
|
527
|
+
let count = 0
|
|
528
|
+
|
|
529
|
+
for (const key of Object.keys(value)) {
|
|
530
|
+
const entryValue = value[key]
|
|
531
|
+
if (typeof entryValue !== 'string' && typeof entryValue !== 'number') continue
|
|
532
|
+
|
|
533
|
+
const keyEntry = stringMap[key] ?? this._cacheString(key)
|
|
534
|
+
const keyEntryLen = keyEntry.length
|
|
535
|
+
const writeOffset = bytes.length
|
|
536
|
+
|
|
537
|
+
if (typeof entryValue === 'string') {
|
|
538
|
+
const valueEntry = stringMap[entryValue] ?? this._cacheString(entryValue)
|
|
539
|
+
const valueEntryLen = valueEntry.length
|
|
540
|
+
bytes.reserve(keyEntryLen + valueEntryLen)
|
|
541
|
+
const target = bytes.buffer
|
|
542
|
+
target.set(keyEntry, writeOffset)
|
|
543
|
+
target.set(valueEntry, writeOffset + keyEntryLen)
|
|
544
|
+
} else {
|
|
545
|
+
bytes.reserve(keyEntryLen + 9)
|
|
546
|
+
const target = bytes.buffer
|
|
547
|
+
target.set(keyEntry, writeOffset)
|
|
548
|
+
const valueOffset = writeOffset + keyEntryLen
|
|
549
|
+
target[valueOffset] = 0xCB
|
|
550
|
+
bytes.view.setFloat64(valueOffset + 1, entryValue)
|
|
551
|
+
}
|
|
552
|
+
count++
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const target = bytes.buffer
|
|
556
|
+
target[countOffset] = count >>> 24
|
|
557
|
+
target[countOffset + 1] = count >>> 16
|
|
558
|
+
target[countOffset + 2] = count >>> 8
|
|
559
|
+
target[countOffset + 3] = count
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Write `[keyPrefix, 8-byte uint64 id]` into `target` at `offset` and
|
|
564
|
+
* return the new cursor. Caller is responsible for having reserved enough
|
|
565
|
+
* room — this is the no-reserve variant used inside `_encode`'s combined
|
|
566
|
+
* fixed-fields block.
|
|
567
|
+
*
|
|
568
|
+
* @param {Uint8Array} target
|
|
569
|
+
* @param {number} offset
|
|
570
|
+
* @param {Buffer} keyPrefix Precomputed `[key, 0xCF]`.
|
|
571
|
+
* @param {{ toBuffer: () => Uint8Array | number[] }} identifier
|
|
572
|
+
* @returns {number}
|
|
573
|
+
*/
|
|
574
|
+
#writeIdAt (target, offset, keyPrefix, identifier) {
|
|
575
|
+
target.set(keyPrefix, offset)
|
|
576
|
+
offset += keyPrefix.length
|
|
577
|
+
const idBuffer = identifier.toBuffer()
|
|
578
|
+
const start = idBuffer.length - 8
|
|
579
|
+
target[offset] = idBuffer[start]
|
|
580
|
+
target[offset + 1] = idBuffer[start + 1]
|
|
581
|
+
target[offset + 2] = idBuffer[start + 2]
|
|
582
|
+
target[offset + 3] = idBuffer[start + 3]
|
|
583
|
+
target[offset + 4] = idBuffer[start + 4]
|
|
584
|
+
target[offset + 5] = idBuffer[start + 5]
|
|
585
|
+
target[offset + 6] = idBuffer[start + 6]
|
|
586
|
+
target[offset + 7] = idBuffer[start + 7]
|
|
587
|
+
return offset + 8
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* @param {MsgpackChunk} bytes
|
|
592
|
+
* @param {Buffer} keyPrefix Precomputed `[key, 0xCE]`.
|
|
593
|
+
* @param {number} value
|
|
594
|
+
*/
|
|
595
|
+
#writeIntegerField (bytes, keyPrefix, value) {
|
|
596
|
+
const keyPrefixLen = keyPrefix.length
|
|
597
|
+
const offset = bytes.length
|
|
598
|
+
bytes.reserve(keyPrefixLen + 4)
|
|
599
|
+
|
|
600
|
+
const target = bytes.buffer
|
|
601
|
+
target.set(keyPrefix, offset)
|
|
602
|
+
|
|
603
|
+
const valueOffset = offset + keyPrefixLen
|
|
604
|
+
target[valueOffset] = value >> 24
|
|
605
|
+
target[valueOffset + 1] = value >> 16
|
|
606
|
+
target[valueOffset + 2] = value >> 8
|
|
607
|
+
target[valueOffset + 3] = value
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* @param {MsgpackChunk} bytes
|
|
612
|
+
* @param {Buffer} keyPrefix Precomputed `[key, 0xCF]`.
|
|
613
|
+
* @param {number} value Up to a 53-bit safe integer.
|
|
614
|
+
*/
|
|
615
|
+
#writeLongField (bytes, keyPrefix, value) {
|
|
616
|
+
const high = (value / 2 ** 32) >> 0
|
|
617
|
+
const low = value >>> 0
|
|
618
|
+
const keyPrefixLen = keyPrefix.length
|
|
619
|
+
const offset = bytes.length
|
|
620
|
+
bytes.reserve(keyPrefixLen + 8)
|
|
621
|
+
|
|
622
|
+
const target = bytes.buffer
|
|
623
|
+
target.set(keyPrefix, offset)
|
|
624
|
+
|
|
625
|
+
const valueOffset = offset + keyPrefixLen
|
|
626
|
+
target[valueOffset] = high >> 24
|
|
627
|
+
target[valueOffset + 1] = high >> 16
|
|
628
|
+
target[valueOffset + 2] = high >> 8
|
|
629
|
+
target[valueOffset + 3] = high
|
|
630
|
+
target[valueOffset + 4] = low >> 24
|
|
631
|
+
target[valueOffset + 5] = low >> 16
|
|
632
|
+
target[valueOffset + 6] = low >> 8
|
|
633
|
+
target[valueOffset + 7] = low
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* @param {MsgpackChunk} bytes
|
|
638
|
+
* @param {string | number | boolean} value
|
|
639
|
+
*/
|
|
640
|
+
#encodeValue (bytes, value) {
|
|
210
641
|
switch (typeof value) {
|
|
211
642
|
case 'string':
|
|
212
643
|
this._encodeString(bytes, value)
|
|
213
644
|
break
|
|
214
645
|
case 'number':
|
|
215
|
-
this
|
|
646
|
+
this.#encodeFloat(bytes, value)
|
|
216
647
|
break
|
|
217
648
|
case 'boolean':
|
|
218
649
|
this._encodeBool(bytes, value)
|
|
219
650
|
break
|
|
220
|
-
default:
|
|
221
|
-
// should not happen
|
|
222
651
|
}
|
|
223
652
|
}
|
|
224
653
|
|
|
225
|
-
|
|
226
|
-
this.
|
|
227
|
-
|
|
228
|
-
const { start, end } = this._stringMap[value]
|
|
229
|
-
|
|
230
|
-
this._stringBytes.copy(bytes, start, end)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
_encodeFloat (bytes, value) {
|
|
234
|
-
this._msgpack.encodeFloat(bytes, value)
|
|
654
|
+
#encodeFloat (bytes, value) {
|
|
655
|
+
this.#msgpack.encodeFloat(bytes, value)
|
|
235
656
|
}
|
|
236
657
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
typeof v === 'number' ||
|
|
243
|
-
(v !== null && typeof v === 'object')
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
this._encodeMapPrefix(bytes, validKeys.length)
|
|
658
|
+
#encodeMetaStruct (bytes, value) {
|
|
659
|
+
if (Array.isArray(value)) {
|
|
660
|
+
this._encodeMapPrefix(bytes, 0)
|
|
661
|
+
return
|
|
662
|
+
}
|
|
247
663
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
664
|
+
const offset = bytes.length
|
|
665
|
+
bytes.reserve(5)
|
|
666
|
+
bytes.buffer[offset] = 0xDF
|
|
667
|
+
|
|
668
|
+
let count = 0
|
|
669
|
+
for (const key of Object.keys(value)) {
|
|
670
|
+
const entryValue = value[key]
|
|
671
|
+
if (typeof entryValue === 'string' || typeof entryValue === 'number' ||
|
|
672
|
+
(entryValue !== null && typeof entryValue === 'object')) {
|
|
673
|
+
this._encodeString(bytes, key)
|
|
674
|
+
this.#encodeObjectAsByteArray(bytes, entryValue)
|
|
675
|
+
count++
|
|
676
|
+
}
|
|
252
677
|
}
|
|
678
|
+
|
|
679
|
+
const target = bytes.buffer
|
|
680
|
+
target[offset + 1] = count >>> 24
|
|
681
|
+
target[offset + 2] = count >>> 16
|
|
682
|
+
target[offset + 3] = count >>> 8
|
|
683
|
+
target[offset + 4] = count
|
|
253
684
|
}
|
|
254
685
|
|
|
255
|
-
|
|
686
|
+
#encodeObjectAsByteArray (bytes, value) {
|
|
256
687
|
const prefixLength = 5
|
|
257
688
|
const offset = bytes.length
|
|
258
689
|
|
|
259
690
|
bytes.reserve(prefixLength)
|
|
260
691
|
|
|
261
|
-
this
|
|
692
|
+
this.#encodeObject(bytes, value)
|
|
262
693
|
|
|
263
|
-
//
|
|
694
|
+
// The byte length isn't known until the inner object has been encoded.
|
|
264
695
|
const length = bytes.length - offset - prefixLength
|
|
265
696
|
bytes.buffer[offset] = 0xC6
|
|
266
697
|
bytes.buffer[offset + 1] = length >> 24
|
|
@@ -269,157 +700,251 @@ class AgentEncoder {
|
|
|
269
700
|
bytes.buffer[offset + 4] = length
|
|
270
701
|
}
|
|
271
702
|
|
|
272
|
-
|
|
703
|
+
#encodeObject (bytes, value, circularReferencesDetector = new Set()) {
|
|
273
704
|
circularReferencesDetector.add(value)
|
|
274
705
|
if (Array.isArray(value)) {
|
|
275
|
-
this
|
|
706
|
+
this.#encodeObjectAsArray(bytes, value, circularReferencesDetector)
|
|
276
707
|
} else if (value !== null && typeof value === 'object') {
|
|
277
|
-
this
|
|
708
|
+
this.#encodeObjectAsMap(bytes, value, circularReferencesDetector)
|
|
278
709
|
} else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
279
|
-
this
|
|
710
|
+
this.#encodeValue(bytes, value)
|
|
280
711
|
}
|
|
281
712
|
}
|
|
282
713
|
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
714
|
+
#encodeObjectAsMap (bytes, value, circularReferencesDetector) {
|
|
715
|
+
const offset = bytes.length
|
|
716
|
+
bytes.reserve(5)
|
|
717
|
+
bytes.buffer[offset] = 0xDF
|
|
718
|
+
|
|
719
|
+
let count = 0
|
|
720
|
+
for (const key of Object.keys(value)) {
|
|
721
|
+
const entryValue = value[key]
|
|
722
|
+
if (typeof entryValue === 'string' || typeof entryValue === 'number' || typeof entryValue === 'boolean' ||
|
|
723
|
+
(entryValue !== null && typeof entryValue === 'object' &&
|
|
724
|
+
!circularReferencesDetector.has(entryValue))) {
|
|
725
|
+
this._encodeString(bytes, key)
|
|
726
|
+
this.#encodeObject(bytes, entryValue, circularReferencesDetector)
|
|
727
|
+
count++
|
|
728
|
+
}
|
|
298
729
|
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
_encodeObjectAsArray (bytes, value, circularReferencesDetector) {
|
|
302
|
-
const validValue = value.filter(item =>
|
|
303
|
-
typeof item === 'string' ||
|
|
304
|
-
typeof item === 'number' ||
|
|
305
|
-
(item !== null && typeof item === 'object' && !circularReferencesDetector.has(item)))
|
|
306
|
-
|
|
307
|
-
this._encodeArrayPrefix(bytes, validValue)
|
|
308
730
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
731
|
+
const target = bytes.buffer
|
|
732
|
+
target[offset + 1] = count >>> 24
|
|
733
|
+
target[offset + 2] = count >>> 16
|
|
734
|
+
target[offset + 3] = count >>> 8
|
|
735
|
+
target[offset + 4] = count
|
|
312
736
|
}
|
|
313
737
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
738
|
+
#encodeObjectAsArray (bytes, value, circularReferencesDetector) {
|
|
739
|
+
const offset = bytes.length
|
|
740
|
+
bytes.reserve(5)
|
|
741
|
+
bytes.buffer[offset] = 0xDD
|
|
742
|
+
|
|
743
|
+
let count = 0
|
|
744
|
+
for (const item of value) {
|
|
745
|
+
if (typeof item === 'string' || typeof item === 'number' ||
|
|
746
|
+
(item !== null && typeof item === 'object' && !circularReferencesDetector.has(item))) {
|
|
747
|
+
this.#encodeObject(bytes, item, circularReferencesDetector)
|
|
748
|
+
count++
|
|
320
749
|
}
|
|
321
750
|
}
|
|
322
|
-
}
|
|
323
751
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
752
|
+
const target = bytes.buffer
|
|
753
|
+
target[offset + 1] = count >>> 24
|
|
754
|
+
target[offset + 2] = count >>> 16
|
|
755
|
+
target[offset + 3] = count >>> 8
|
|
756
|
+
target[offset + 4] = count
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Specialized encoder for `span.span_events`. Walks the events directly,
|
|
761
|
+
* emitting OTel attribute typed wrappers inline from the raw attribute
|
|
762
|
+
* values — no `formatSpanEvents` pre-pass and no recursive generic walk.
|
|
763
|
+
*
|
|
764
|
+
* @param {MsgpackChunk} bytes
|
|
765
|
+
* @param {Array<{ name: string, time_unix_nano: number, attributes?: object }>} spanEvents
|
|
766
|
+
*/
|
|
767
|
+
#encodeSpanEvents (bytes, spanEvents) {
|
|
768
|
+
const offset = bytes.length
|
|
769
|
+
bytes.reserve(5)
|
|
770
|
+
bytes.buffer[offset] = 0xDD
|
|
327
771
|
|
|
328
|
-
|
|
329
|
-
|
|
772
|
+
let arrayCount = 0
|
|
773
|
+
for (const event of spanEvents) {
|
|
774
|
+
// `addEvent` and the OTel bridge do not type-check `name`, and a
|
|
775
|
+
// non-string would throw downstream in `Buffer.byteLength`. Drop the
|
|
776
|
+
// bad event silently so the rest of the trace still encodes.
|
|
777
|
+
if (event === null || typeof event !== 'object' || typeof event.name !== 'string') continue
|
|
330
778
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
779
|
+
const eventHeaderOffset = bytes.length
|
|
780
|
+
bytes.reserve(1)
|
|
781
|
+
bytes.buffer[eventHeaderOffset] = 0x82
|
|
334
782
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
783
|
+
bytes.set(KEY_NAME)
|
|
784
|
+
this._encodeString(bytes, event.name)
|
|
785
|
+
bytes.set(KEY_EVENT_TIME)
|
|
786
|
+
this.#encodeFloat(bytes, event.time_unix_nano)
|
|
338
787
|
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
seenKeys.add(key)
|
|
343
|
-
log.debug(message)
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function formatSpanEvents (span) {
|
|
348
|
-
for (const spanEvent of span.span_events) {
|
|
349
|
-
if (spanEvent.attributes) {
|
|
350
|
-
let hasAttributes = false
|
|
351
|
-
for (const [key, value] of Object.entries(spanEvent.attributes)) {
|
|
352
|
-
const newValue = convertSpanEventAttributeValues(key, value)
|
|
353
|
-
if (newValue === undefined) {
|
|
354
|
-
delete spanEvent.attributes[key] // delete from attributes if undefined
|
|
355
|
-
} else {
|
|
356
|
-
hasAttributes = true
|
|
357
|
-
spanEvent.attributes[key] = newValue
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
if (!hasAttributes) {
|
|
361
|
-
delete spanEvent.attributes
|
|
788
|
+
const attributes = event.attributes
|
|
789
|
+
if (attributes !== null && typeof attributes === 'object') {
|
|
790
|
+
this.#encodeAttributesIfAny(bytes, attributes, eventHeaderOffset)
|
|
362
791
|
}
|
|
792
|
+
arrayCount++
|
|
363
793
|
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
794
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
795
|
+
const target = bytes.buffer
|
|
796
|
+
target[offset + 1] = arrayCount >>> 24
|
|
797
|
+
target[offset + 2] = arrayCount >>> 16
|
|
798
|
+
target[offset + 3] = arrayCount >>> 8
|
|
799
|
+
target[offset + 4] = arrayCount
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Optimistically emits the `'attributes'` slot for an event. If every entry
|
|
804
|
+
* filters out, the partial output is rewound and the event's map header
|
|
805
|
+
* stays at 2 entries.
|
|
806
|
+
*
|
|
807
|
+
* @param {MsgpackChunk} bytes
|
|
808
|
+
* @param {Record<string, unknown>} attributes
|
|
809
|
+
* @param {number} eventHeaderOffset
|
|
810
|
+
*/
|
|
811
|
+
#encodeAttributesIfAny (bytes, attributes, eventHeaderOffset) {
|
|
812
|
+
const sectionStart = bytes.length
|
|
813
|
+
|
|
814
|
+
bytes.set(KEY_EVENT_ATTRIBUTES)
|
|
815
|
+
const countOffset = bytes.length
|
|
816
|
+
bytes.reserve(5)
|
|
817
|
+
bytes.buffer[countOffset] = 0xDF
|
|
818
|
+
|
|
819
|
+
let count = 0
|
|
820
|
+
for (const key of Object.keys(attributes)) {
|
|
821
|
+
if (this.#emitAttribute(bytes, key, attributes[key])) count++
|
|
372
822
|
}
|
|
373
|
-
}
|
|
374
823
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
bool_value: value,
|
|
824
|
+
if (count === 0) {
|
|
825
|
+
bytes.length = sectionStart
|
|
826
|
+
return
|
|
379
827
|
}
|
|
380
|
-
}
|
|
381
828
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
829
|
+
const target = bytes.buffer
|
|
830
|
+
target[countOffset + 1] = count >>> 24
|
|
831
|
+
target[countOffset + 2] = count >>> 16
|
|
832
|
+
target[countOffset + 3] = count >>> 8
|
|
833
|
+
target[countOffset + 4] = count
|
|
834
|
+
bytes.buffer[eventHeaderOffset] = 0x83
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Emit `<key, typed_wrapper>` for a single attribute. Returns true on a
|
|
839
|
+
* supported type, false (with a memoized debug log) otherwise.
|
|
840
|
+
*
|
|
841
|
+
* @param {MsgpackChunk} bytes
|
|
842
|
+
* @param {string} key
|
|
843
|
+
* @param {unknown} value
|
|
844
|
+
* @returns {boolean}
|
|
845
|
+
*/
|
|
846
|
+
#emitAttribute (bytes, key, value) {
|
|
847
|
+
if (typeof value === 'string') {
|
|
848
|
+
this._encodeString(bytes, key)
|
|
849
|
+
bytes.set(ATTR_PREFIX_STRING)
|
|
850
|
+
this._encodeString(bytes, value)
|
|
851
|
+
return true
|
|
388
852
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
853
|
+
if (typeof value === 'number') {
|
|
854
|
+
this._encodeString(bytes, key)
|
|
855
|
+
bytes.set(Number.isInteger(value) ? ATTR_PREFIX_INT : ATTR_PREFIX_DOUBLE)
|
|
856
|
+
this.#encodeFloat(bytes, value)
|
|
857
|
+
return true
|
|
858
|
+
}
|
|
859
|
+
if (typeof value === 'boolean') {
|
|
860
|
+
this._encodeString(bytes, key)
|
|
861
|
+
bytes.set(value ? ATTR_PAYLOAD_BOOL_TRUE : ATTR_PAYLOAD_BOOL_FALSE)
|
|
862
|
+
return true
|
|
863
|
+
}
|
|
864
|
+
if (Array.isArray(value)) {
|
|
865
|
+
return this.#emitArrayAttribute(bytes, key, value)
|
|
866
|
+
}
|
|
867
|
+
memoizedLogDebug(key, 'Encountered unsupported data type for span event v0.4 encoding, key: ' +
|
|
868
|
+
`${key}: with value: ${typeof value}. Skipping encoding of pair.`
|
|
869
|
+
)
|
|
870
|
+
return false
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Emit `<key, { type: 4, array_value: { values: [...] } }>` from a raw
|
|
875
|
+
* array of primitives. Filters nested arrays / unsupported items; if no
|
|
876
|
+
* items remain the whole entry is rewound.
|
|
877
|
+
*
|
|
878
|
+
* @param {MsgpackChunk} bytes
|
|
879
|
+
* @param {string} key
|
|
880
|
+
* @param {Array<unknown>} array
|
|
881
|
+
* @returns {boolean}
|
|
882
|
+
*/
|
|
883
|
+
#emitArrayAttribute (bytes, key, array) {
|
|
884
|
+
const sectionStart = bytes.length
|
|
885
|
+
|
|
886
|
+
this._encodeString(bytes, key)
|
|
887
|
+
bytes.set(ATTR_PREFIX_ARRAY)
|
|
888
|
+
const arrayCountOffset = bytes.length
|
|
889
|
+
bytes.reserve(4)
|
|
890
|
+
|
|
891
|
+
let count = 0
|
|
892
|
+
for (const item of array) {
|
|
893
|
+
if (this.#emitArrayItem(bytes, key, item)) count++
|
|
392
894
|
}
|
|
393
|
-
}
|
|
394
895
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const convertedVal = convertSpanEventAttributeValues(key, val, 1)
|
|
400
|
-
if (convertedVal !== undefined) {
|
|
401
|
-
convertedArray.push(convertedVal)
|
|
402
|
-
}
|
|
403
|
-
}
|
|
896
|
+
if (count === 0) {
|
|
897
|
+
bytes.length = sectionStart
|
|
898
|
+
return false
|
|
899
|
+
}
|
|
404
900
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
901
|
+
const target = bytes.buffer
|
|
902
|
+
target[arrayCountOffset] = count >>> 24
|
|
903
|
+
target[arrayCountOffset + 1] = count >>> 16
|
|
904
|
+
target[arrayCountOffset + 2] = count >>> 8
|
|
905
|
+
target[arrayCountOffset + 3] = count
|
|
906
|
+
return true
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Emit a single typed wrapper inside an `array_value.values` array. No
|
|
911
|
+
* recursion: nested arrays are dropped with a memoized debug log.
|
|
912
|
+
*
|
|
913
|
+
* @param {MsgpackChunk} bytes
|
|
914
|
+
* @param {string} key
|
|
915
|
+
* @param {unknown} value
|
|
916
|
+
* @returns {boolean}
|
|
917
|
+
*/
|
|
918
|
+
#emitArrayItem (bytes, key, value) {
|
|
919
|
+
if (typeof value === 'string') {
|
|
920
|
+
bytes.set(ATTR_PREFIX_STRING)
|
|
921
|
+
this._encodeString(bytes, value)
|
|
922
|
+
return true
|
|
923
|
+
}
|
|
924
|
+
if (typeof value === 'number') {
|
|
925
|
+
bytes.set(Number.isInteger(value) ? ATTR_PREFIX_INT : ATTR_PREFIX_DOUBLE)
|
|
926
|
+
this.#encodeFloat(bytes, value)
|
|
927
|
+
return true
|
|
928
|
+
}
|
|
929
|
+
if (typeof value === 'boolean') {
|
|
930
|
+
bytes.set(value ? ATTR_PAYLOAD_BOOL_TRUE : ATTR_PAYLOAD_BOOL_FALSE)
|
|
931
|
+
return true
|
|
932
|
+
}
|
|
933
|
+
if (Array.isArray(value)) {
|
|
414
934
|
memoizedLogDebug(key, 'Encountered nested array data type for span event v0.4 encoding. ' +
|
|
415
935
|
`Skipping encoding key: ${key}: with value: ${typeof value}.`
|
|
416
936
|
)
|
|
417
937
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
938
|
+
return false
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const seenKeys = new Set()
|
|
943
|
+
function memoizedLogDebug (key, message) {
|
|
944
|
+
if (!seenKeys.has(key)) {
|
|
945
|
+
seenKeys.add(key)
|
|
946
|
+
log.debug(message)
|
|
422
947
|
}
|
|
423
948
|
}
|
|
424
949
|
|
|
425
|
-
module.exports = { AgentEncoder }
|
|
950
|
+
module.exports = { AgentEncoder, stringifySpanEvents }
|