Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add span events as a top level field for v0.4 encoding #5229

Merged
merged 38 commits into from
Mar 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c6ca73b
span events top level
wconti27 Jan 30, 2025
8c7eb22
only serialize span events as top level if agent returns span_events …
wconti27 Feb 7, 2025
96dd7c5
fix bug
wconti27 Feb 7, 2025
efa08ef
fix asertion
wconti27 Feb 7, 2025
5c9fdd4
fix var
wconti27 Feb 10, 2025
4e858ea
add test (that is currently failing - SAD)
wconti27 Feb 10, 2025
9ac1149
revert a few changes
wconti27 Feb 10, 2025
5d3001c
add encoding logic
wconti27 Feb 10, 2025
7bb441d
increment map size if span_events field is present
wconti27 Feb 10, 2025
c4ed389
fix check
wconti27 Feb 10, 2025
ccba8bb
try catch around fetch agent info
wconti27 Feb 10, 2025
136a7da
fix bug
wconti27 Feb 10, 2025
4e5b659
fetch agent info if config is true
wconti27 Mar 3, 2025
399d81e
Merge branch 'master' into conti/serialize_span_events_as_top_level_f…
wconti27 Mar 3, 2025
799cf89
Merge branch 'master' into conti/serialize_span_events_as_top_level_f…
wconti27 Mar 4, 2025
8d83b65
Update packages/dd-trace/src/encode/0.5.js
wconti27 Mar 4, 2025
dabc7a2
Update packages/dd-trace/src/encode/0.5.js
wconti27 Mar 4, 2025
f2b381d
Update packages/dd-trace/src/encode/0.4.js
wconti27 Mar 4, 2025
e4cc2bb
add agent discovery
wconti27 Mar 4, 2025
b4172c1
change value to null
wconti27 Mar 4, 2025
960c07d
fix small errors
wconti27 Mar 4, 2025
15cca5b
move around some things
wconti27 Mar 5, 2025
cabf2c0
pass encoder to formatSpan
wconti27 Mar 5, 2025
94e5b70
fix lint
wconti27 Mar 5, 2025
5222c05
get rid of agent discovery and instead just use env configuration
wconti27 Mar 5, 2025
d9dacf4
fix some things
wconti27 Mar 5, 2025
d75045b
fix format tests
wconti27 Mar 5, 2025
afe0221
fix encoder tests
wconti27 Mar 5, 2025
55faa88
fix tests
wconti27 Mar 5, 2025
2c6a9d2
Merge branch 'master' into conti/serialize_span_events_as_top_level_f…
wconti27 Mar 5, 2025
65f3b26
Merge branch 'master' into conti/serialize_span_events_as_top_level_f…
wconti27 Mar 5, 2025
2acd38f
small change
wconti27 Mar 5, 2025
357938b
encode attributes with type map
wconti27 Mar 6, 2025
dcebbb7
fix config initialization
wconti27 Mar 6, 2025
c6f9def
Merge branch 'master' into conti/serialize_span_events_as_top_level_f…
wconti27 Mar 17, 2025
8185395
fix reviewer comments
wconti27 Mar 17, 2025
fce4d92
fix log messages to be memoized and add tests for memoization
wconti27 Mar 18, 2025
d4e9fbe
Merge branch 'master' into conti/serialize_span_events_as_top_level_f…
wconti27 Mar 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/dd-trace/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ class Config {
this._setValue(defaults, 'vertexai.spanCharLimit', 128)
this._setValue(defaults, 'vertexai.spanPromptCompletionSampleRate', 1.0)
this._setValue(defaults, 'trace.aws.addSpanPointers', true)
this._setValue(defaults, 'trace.nativeSpanEvents', false)
}

_applyLocalStableConfig () {
Expand Down Expand Up @@ -765,6 +766,7 @@ class Config {
DD_VERTEXAI_SPAN_PROMPT_COMPLETION_SAMPLE_RATE,
DD_VERTEXAI_SPAN_CHAR_LIMIT,
DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED,
DD_TRACE_NATIVE_SPAN_EVENTS,
OTEL_METRICS_EXPORTER,
OTEL_PROPAGATORS,
OTEL_RESOURCE_ATTRIBUTES,
Expand Down Expand Up @@ -977,6 +979,7 @@ class Config {
this._setBoolean(env, 'trace.aws.addSpanPointers', DD_TRACE_AWS_ADD_SPAN_POINTERS)
this._setString(env, 'trace.dynamoDb.tablePrimaryKeys', DD_TRACE_DYNAMODB_TABLE_PRIMARY_KEYS)
this._setArray(env, 'graphqlErrorExtensions', DD_TRACE_GRAPHQL_ERROR_EXTENSIONS)
this._setBoolean(env, 'trace.nativeSpanEvents', DD_TRACE_NATIVE_SPAN_EVENTS)
this._setValue(
env,
'vertexai.spanPromptCompletionSampleRate',
Expand Down Expand Up @@ -1114,6 +1117,7 @@ class Config {
this._setString(opts, 'version', options.version || tags.version)
this._setBoolean(opts, 'inferredProxyServicesEnabled', options.inferredProxyServicesEnabled)
this._setBoolean(opts, 'graphqlErrorExtensions', options.graphqlErrorExtensions)
this._setBoolean(opts, 'trace.nativeSpanEvents', options.trace?.nativeSpanEvents)

// For LLMObs, we want the environment variable to take precedence over the options.
// This is reliant on environment config being set before options.
Expand Down
120 changes: 108 additions & 12 deletions packages/dd-trace/src/encode/0.4.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,22 @@ const { Chunk, MsgpackEncoder } = require('../msgpack')
const log = require('../log')
const { isTrue } = require('../util')
const coalesce = require('koalas')
const { memoize } = require('../log/utils')

const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB

function formatSpan (span) {
return normalizeSpan(truncateSpan(span, false))
function formatSpan (span, config) {
span = normalizeSpan(truncateSpan(span, false))
if (span.span_events) {
// ensure span events are encoded as tags if agent doesn't support native top level span events
if (!config?.trace?.nativeSpanEvents) {
span.meta.events = JSON.stringify(span.span_events)
delete span.span_events
} else {
formatSpanEvents(span)
}
}
return span
}

class AgentEncoder {
Expand All @@ -24,6 +35,7 @@ class AgentEncoder {
process.env.DD_TRACE_ENCODING_DEBUG,
false
))
this._config = this._writer?._config
}

count () {
Expand Down Expand Up @@ -74,16 +86,18 @@ class AgentEncoder {
this._encodeArrayPrefix(bytes, trace)

for (let span of trace) {
span = formatSpan(span)
span = formatSpan(span, this._config)
bytes.reserve(1)

if (span.type && span.meta_struct) {
bytes.buffer[bytes.length - 1] = 0x8d
} else if (span.type || span.meta_struct) {
bytes.buffer[bytes.length - 1] = 0x8c
} else {
bytes.buffer[bytes.length - 1] = 0x8b
}
// this is the original size of the fixed map for span attributes that always exist
let mapSize = 11

// increment the payload map size depending on if some optional fields exist
if (span.type) mapSize += 1
if (span.meta_struct) mapSize += 1
if (span.span_events) mapSize += 1

bytes.buffer[bytes.length - 1] = 0x80 + mapSize

if (span.type) {
this._encodeString(bytes, 'type')
Expand Down Expand Up @@ -112,6 +126,10 @@ class AgentEncoder {
this._encodeMap(bytes, span.meta)
this._encodeString(bytes, 'metrics')
this._encodeMap(bytes, span.metrics)
if (span.span_events) {
this._encodeString(bytes, 'span_events')
this._encodeObjectAsArray(bytes, span.span_events, new Set())
}
if (span.meta_struct) {
this._encodeString(bytes, 'meta_struct')
this._encodeMetaStruct(bytes, span.meta_struct)
Expand Down Expand Up @@ -200,6 +218,9 @@ class AgentEncoder {
case 'number':
this._encodeFloat(bytes, value)
break
case 'boolean':
this._encodeBool(bytes, value)
break
default:
// should not happen
}
Expand Down Expand Up @@ -258,7 +279,7 @@ class AgentEncoder {
this._encodeObjectAsArray(bytes, value, circularReferencesDetector)
} else if (value !== null && typeof value === 'object') {
this._encodeObjectAsMap(bytes, value, circularReferencesDetector)
} else if (typeof value === 'string' || typeof value === 'number') {
} else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
this._encodeValue(bytes, value)
}
}
Expand All @@ -268,7 +289,7 @@ class AgentEncoder {
const validKeys = keys.filter(key => {
const v = value[key]
return typeof v === 'string' ||
typeof v === 'number' ||
typeof v === 'number' || typeof v === 'boolean' ||
(v !== null && typeof v === 'object' && !circularReferencesDetector.has(v))
})

Expand Down Expand Up @@ -319,4 +340,79 @@ class AgentEncoder {
}
}

const memoizedLogDebug = memoize((key, message) => {
log.debug(message)
// return something to store in memoize cache
return true
})

function formatSpanEvents (span) {
for (const spanEvent of span.span_events) {
if (spanEvent.attributes) {
for (const [key, value] of Object.entries(spanEvent.attributes)) {
const newValue = convertSpanEventAttributeValues(key, value)
if (newValue !== undefined) {
spanEvent.attributes[key] = newValue
} else {
delete spanEvent.attributes[key] // delete from attributes if undefined
}
}
if (Object.entries(spanEvent.attributes).length === 0) {
delete spanEvent.attributes
}
}
}
}

function convertSpanEventAttributeValues (key, value, depth = 0) {
if (typeof value === 'string') {
return {
type: 0,
string_value: value
}
} else if (typeof value === 'boolean') {
return {
type: 1,
bool_value: value
}
} else if (Number.isInteger(value)) {
return {
type: 2,
int_value: value
}
} else if (typeof value === 'number') {
return {
type: 3,
double_value: value
}
} else if (Array.isArray(value)) {
if (depth === 0) {
const convertedArray = value
.map((val) => convertSpanEventAttributeValues(key, val, 1))
.filter((convertedVal) => convertedVal !== undefined)

// Only include array_value if there are valid elements
if (convertedArray.length > 0) {
return {
type: 4,
array_value: convertedArray
}
} else {
// If all elements were unsupported, return undefined
return undefined
}
} else {
memoizedLogDebug(key, 'Encountered nested array data type for span event v0.4 encoding. ' +
`Skipping encoding key: ${key}: with value: ${typeof value}.`
)
return undefined
}
} else {
memoizedLogDebug(key, 'Encountered unsupported data type for span event v0.4 encoding, key: ' +
`${key}: with value: ${typeof value}. Skipping encoding of pair.`
)
return undefined
}
}

module.exports = { AgentEncoder }
8 changes: 7 additions & 1 deletion packages/dd-trace/src/encode/0.5.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ const ARRAY_OF_TWO = 0x92
const ARRAY_OF_TWELVE = 0x9c

function formatSpan (span) {
return normalizeSpan(truncateSpan(span, false))
span = normalizeSpan(truncateSpan(span, false))
// ensure span events are encoded as tags
if (span.span_events) {
span.meta.events = JSON.stringify(span.span_events)
delete span.span_events
}
return span
}

class AgentEncoder extends BaseEncoder {
Expand Down
3 changes: 2 additions & 1 deletion packages/dd-trace/src/exporters/agent/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ class AgentExporter {
prioritySampler,
lookup,
protocolVersion,
headers
headers,
config
})

this._timer = undefined
Expand Down
5 changes: 3 additions & 2 deletions packages/dd-trace/src/exporters/agent/writer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ const BaseWriter = require('../common/writer')
const METRIC_PREFIX = 'datadog.tracer.node.exporter.agent'

class Writer extends BaseWriter {
constructor ({ prioritySampler, lookup, protocolVersion, headers }) {
constructor ({ prioritySampler, lookup, protocolVersion, headers, config = {} }) {
super(...arguments)
const AgentEncoder = getEncoder(protocolVersion)

this._prioritySampler = prioritySampler
this._lookup = lookup
this._protocolVersion = protocolVersion
this._encoder = new AgentEncoder(this)
this._headers = headers
this._config = config
this._encoder = new AgentEncoder(this)
}

_sendPayload (data, count, done) {
Expand Down
Loading
Loading