Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
3ab5862
Set known tags by id on TagMap and spans
dougqh Oct 9, 2026
4bf02bb
Ignore unknown ids when clearing a tag by id; fix a comment wrap
dougqh Oct 9, 2026
a2829d1
Precheck and reuse the box in long-valued setters
dougqh Oct 9, 2026
2d87cde
Keep peer.port off the id path
dougqh Oct 9, 2026
7a017a5
Merge set-by-id (#12715) onto master
dougqh Oct 9, 2026
0401a43
Mark intercepted tags with an id bit, via a tracer overlay
dougqh Oct 9, 2026
8949a57
Dispatch TagInterceptor on tag ids
dougqh Oct 9, 2026
cfe2368
Set tags on spans by id first
dougqh Oct 9, 2026
38c9947
Look up tag names by serial in an array, not a switch
dougqh Oct 9, 2026
3545c15
Point stale comments at the tracer overlay and the intercepted bit
dougqh Oct 9, 2026
1642cc9
Index the split-by-tags table by serial, sized to every known tag
dougqh Oct 9, 2026
b9dd573
Check for a known tag id with a range check, not a name lookup
dougqh Oct 9, 2026
75d3fbc
Pin interceptTag(long) above C2's inlining size
dougqh Oct 9, 2026
ecb4f73
Tighten comments on the id-keyed interceptor
dougqh Oct 9, 2026
3f65031
Treat overlay tags as intercepted; tidy from review
dougqh Oct 9, 2026
69d7849
Reject shared-name tag ids on the id-keyed TagMap and span paths
dougqh Oct 9, 2026
46c3b47
Test rejection of shared-name tag ids
dougqh Oct 9, 2026
ad2fa97
Build custom tags by name and known tags by id
dougqh Oct 10, 2026
628da33
Merge master into id-tag-interceptor (#12817)
dougqh Oct 10, 2026
dac7060
Give the tracer core a restricted, trusted path into TagMap
dougqh Oct 10, 2026
fbab551
Mark TagMap.Internals @NoEscape
dougqh Oct 10, 2026
f08c1c3
Add datadog.apicontract to CODEOWNERS
dougqh Oct 13, 2026
f796149
Merge branch 'master' into dougqh/id-tag-interceptor
dougqh Oct 13, 2026
22d0fd1
Give peer.port one direction-neutral id
dougqh Oct 13, 2026
ba715de
Name peer.port by the span's kind in OTLP export
dougqh Oct 13, 2026
584310c
Let C2 inline the constructors of TagMap's nested classes
dougqh Oct 11, 2026
3842b16
Remove TagMap's remaining synthetic accessors and guard against them
dougqh Oct 11, 2026
64d6cf3
Merge master into id-tag-interceptor
dougqh Oct 14, 2026
bb0bbb0
Cover TagMap.Internals, KnownTagCodec and EntryReader paths
dougqh Oct 14, 2026
fb6410e
Format the tag-registry generator to its spotless rules
dougqh Oct 14, 2026
97a508b
Order the tag-id carve bits by position
dougqh Oct 14, 2026
fa54aca
Merge master into id-tag-interceptor
dougqh Oct 14, 2026
1c88d5e
Re-apply split-by-tags language by its id
dougqh Oct 14, 2026
5ba0a09
Say what a tag id's intercepted bit does and doesn't decide
dougqh Oct 14, 2026
5b4b320
Ratchet the tag constants toward full registry coverage
dougqh Oct 15, 2026
9ac6e59
Let the tracer overlay declare mixins on shared span types
dougqh Oct 15, 2026
6e7b20c
Register HTTP, DB, websocket and core internal tags
dougqh Oct 15, 2026
02d56c2
Register messaging, RPC and Data Streams tags
dougqh Oct 15, 2026
62995f5
Register AWS SDK tags, with a span type per AWS service
dougqh Oct 15, 2026
82fc726
Let the tracer overlay declare span types
dougqh Oct 15, 2026
57fdd49
Model Java-only extensions as span types, not mixins
dougqh Oct 15, 2026
c381b28
Register code origin as a product mixin, without its frame names
dougqh Oct 15, 2026
89e0803
Register Elasticsearch, Twilio, JSP, Quartz and Hystrix tags; match p…
dougqh Oct 15, 2026
b2d4174
Register the remaining APM tag constants
dougqh Oct 15, 2026
e3f528d
Split db.client into relational and per-system NoSQL types
dougqh Oct 15, 2026
16bcf0b
Merge master into tag-registry-coverage
dougqh Oct 15, 2026
20a0148
Put span types first in the Java overlay
dougqh Oct 15, 2026
9177a93
Add peer tags to messaging spans; apply code origin to handler spans
dougqh Oct 15, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,47 @@ class TagConventions private constructor(
)
}

/** A span type declaration, from the shared conventions or a tracer overlay. */
@Suppress("UNCHECKED_CAST")
private fun parseSpanType(name: String, v: Any?): SpanType {
require(v is Map<*, *>) { "span type '$name' must be a mapping" }
val m = v as Map<String, Any?>
require(m["abstract"] == null || m["abstract"] is Boolean) { "span type '$name' abstract must be a boolean" }
require(m["extends"] == null || m["extends"] is String) { "span type '$name' extends must be a span type name" }
val include = m["include"]
require(include == null || (include is List<*> && include.all { it is String })) {
"span type '$name' include must be a list of mixin names"
}
return SpanType(
name = name,
abstract = (m["abstract"] as? Boolean) ?: false,
extends = m["extends"] as? String,
include = (m["include"] as? List<String>) ?: emptyList(),
tags = tagList(m["tags"]),
refs = refList(m["tags"]),
direction = parseDirection(m, "span type '$name'"),
)
}

/** A mixin declaration, from the shared conventions or a tracer overlay. */
@Suppress("UNCHECKED_CAST")
private fun parseMixin(name: String, v: Any?): Mixin {
require(v is Map<*, *>) { "mixin '$name' must be a mapping" }
val m = v as Map<String, Any?>
val applies = m["applies"]
require(applies == null || applies == "all" || (applies is List<*> && applies.all { it is String })) {
"mixin '$name' applies must be 'all' or a list of span types"
}
return Mixin(
name = name,
appliesAll = applies == "all",
appliesTo = if (applies is List<*>) (applies as List<String>).toSet() else emptySet(),
tags = tagList(m["tags"]),
refs = refList(m["tags"]),
direction = parseDirection(m, "mixin '$name'"),
)
}

/** Reads `span-kind` as the direction it sets, or null when absent. */
private fun parseDirection(m: Map<String, Any?>, owner: String): Direction? {
val spanKind = m["span-kind"]
Expand All @@ -262,17 +303,21 @@ class TagConventions private constructor(
const val OVERLAY_MIXIN = "tracer overlay"

/**
* Parses [root], the language-agnostic conventions, plus [overlay], this tracer's own set-path
* routing: `tags` declares keys that exist only to be routed (e.g. `resource.name`), so each is
* intercepted, and `intercepted` adds the tags declared in [root] that the tracer also routes.
* Parses [root], the language-agnostic conventions, plus [overlay], this tracer's own additions:
* `tags` declares keys that exist only to be routed (e.g. `resource.name`), so each is
* intercepted; `span_types` declares this tracer's own span types, each extending a shared one
* (e.g. servlet's request extends http.server); `mixins` declares this tracer's cross-cutting
* tags, each mixin pushed onto span types with `applies`; and `intercepted` adds the declared
* tags the tracer also routes.
*/
@Suppress("UNCHECKED_CAST")
fun parse(root: Map<String, Any?>, overlay: Map<String, Any?> = emptyMap()): TagConventions {
for (section in listOf("span_types", "mixins", "trace_level")) {
require(root[section] == null || root[section] is Map<*, *>) { "$section must be a mapping" }
}
require(overlay.keys.all { it == "tags" || it == "intercepted" }) {
"the tracer overlay may only declare `tags` and `intercepted`, not ${overlay.keys - setOf("tags", "intercepted")}"
val overlayKeys = setOf("tags", "span_types", "mixins", "intercepted")
require(overlay.keys.all { it in overlayKeys }) {
"the tracer overlay may only declare $overlayKeys, not ${overlay.keys - overlayKeys}"
}
require(refList(overlay["tags"]).isEmpty()) { "tracer overlay tags must be declarations, not refs" }
val interceptedRaw = overlay["intercepted"]
Expand All @@ -283,50 +328,39 @@ class TagConventions private constructor(
val intercepted =
((interceptedRaw as? List<String>) ?: emptyList()).toSet() +
tagList(overlay["tags"]).map { it.ddName }
val spanTypesRaw = (root["span_types"] as? Map<String, Any?>) ?: emptyMap()
val parsedSpanTypes =
spanTypesRaw.mapValues { (name, v) ->
require(v is Map<*, *>) { "span type '$name' must be a mapping" }
val m = v as Map<String, Any?>
require(m["abstract"] == null || m["abstract"] is Boolean) {
"span type '$name' abstract must be a boolean"
}
require(m["extends"] == null || m["extends"] is String) {
"span type '$name' extends must be a span type name"
}
val include = m["include"]
require(include == null || (include is List<*> && include.all { it is String })) {
"span type '$name' include must be a list of mixin names"
}
SpanType(
name = name,
abstract = (m["abstract"] as? Boolean) ?: false,
extends = m["extends"] as? String,
include = (m["include"] as? List<String>) ?: emptyList(),
tags = tagList(m["tags"]),
refs = refList(m["tags"]),
direction = parseDirection(m, "span type '$name'"),
)
val rootSpanTypes = ((root["span_types"] as? Map<String, Any?>) ?: emptyMap()).mapValues { (name, v) -> parseSpanType(name, v) }
require(overlay["span_types"] == null || overlay["span_types"] is Map<*, *>) {
"tracer overlay span_types must be a mapping"
}
// A tracer's span type extends a shared concept with tags only this tracer sets (servlet's
// request on http.server, say).
val overlaySpanTypes =
((overlay["span_types"] as? Map<String, Any?>) ?: emptyMap()).mapValues { (name, v) ->
require(name !in rootSpanTypes) { "tracer overlay span type '$name' reuses the name of a shared span type" }
val spanType = parseSpanType(name, v)
require(spanType.extends != null) { "tracer overlay span type '$name' must extend a span type" }
spanType
}
val parsedSpanTypes = rootSpanTypes + overlaySpanTypes

val mixinsRaw = (root["mixins"] as? Map<String, Any?>) ?: emptyMap()
val parsedMixins =
mixinsRaw.mapValues { (name, v) ->
require(v is Map<*, *>) { "mixin '$name' must be a mapping" }
val m = v as Map<String, Any?>
val applies = m["applies"]
require(applies == null || applies == "all" || (applies is List<*> && applies.all { it is String })) {
"mixin '$name' applies must be 'all' or a list of span types"
require(overlay["mixins"] == null || overlay["mixins"] is Map<*, *>) {
"tracer overlay mixins must be a mapping"
}
val rootMixins = ((root["mixins"] as? Map<String, Any?>) ?: emptyMap()).mapValues { (name, v) -> parseMixin(name, v) }
// A shared span type can't `include` a tracer's mixin by name, so an overlay mixin pushes
// itself onto shared types with `applies`.
val overlayMixins =
((overlay["mixins"] as? Map<String, Any?>) ?: emptyMap()).mapValues { (name, v) ->
require(name !in rootMixins && name != OVERLAY_MIXIN) {
"tracer overlay mixin '$name' reuses the name of a shared mixin"
}
Mixin(
name = name,
appliesAll = applies == "all",
appliesTo = if (applies is List<*>) (applies as List<String>).toSet() else emptySet(),
tags = tagList(m["tags"]),
refs = refList(m["tags"]),
direction = parseDirection(m, "mixin '$name'"),
)
} + overlayMixin(overlay)
val mixin = parseMixin(name, v)
require(mixin.appliesAll || mixin.appliesTo.isNotEmpty()) {
"tracer overlay mixin '$name' must say which span types it applies to"
}
mixin
}
val parsedMixins = rootMixins + overlayMixins + overlayMixin(overlay)

for (spanType in parsedSpanTypes.values) {
for (included in spanType.include) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,17 +543,87 @@ class TagRegistryGeneratorTest {
)
}

@Test
fun `a tracer overlay mixin applies its tags to shared span types`() {
val mapper = ObjectMapper(YAMLFactory())
val type = object : TypeReference<Map<String, Any?>>() {}
val root: Map<String, Any?> =
mapper.readValue(
"""
span_types:
base: {abstract: true, tags: [{dd-name: service}]}
http.server: {extends: base, span-kind: server, tags: [{dd-name: http.route}]}
db.client: {extends: base, span-kind: client, tags: [{dd-name: db.type}]}
""",
type
)
val overlay: Map<String, Any?> =
mapper.readValue(
"""
mixins:
servlet: {applies: [http.server], tags: [{dd-name: servlet.context}, {dd-name: servlet.path}]}
intercepted: [servlet.context]
""",
type
)

val conv = TagConventions.parse(root, overlay)

assertThat(conv.resolve("http.server").map { it.ddName })
.containsExactlyInAnyOrder("service", "http.route", "servlet.context", "servlet.path")
assertThat(conv.resolve("db.client").map { it.ddName }).containsExactlyInAnyOrder("service", "db.type")
assertThat(conv.interceptedNames).contains("servlet.context")
}

@Test
fun `a tracer overlay span type extends a shared one`() {
val mapper = ObjectMapper(YAMLFactory())
val type = object : TypeReference<Map<String, Any?>>() {}
val root: Map<String, Any?> =
mapper.readValue(
"""
span_types:
base: {abstract: true, tags: [{dd-name: service}]}
http.server: {extends: base, span-kind: server, tags: [{dd-name: http.route}]}
""",
type
)
val overlay: Map<String, Any?> =
mapper.readValue(
"""
span_types:
servlet.request: {extends: http.server, tags: [{dd-name: servlet.context}, {dd-name: servlet.path}]}
intercepted: [servlet.context]
""",
type
)

val conv = TagConventions.parse(root, overlay)

assertThat(conv.concreteTypes()).contains("http.server", "servlet.request")
assertThat(conv.resolve("servlet.request").map { it.ddName })
.containsExactlyInAnyOrder("service", "http.route", "servlet.context", "servlet.path")
assertThat(conv.resolve("http.server").map { it.ddName }).containsExactlyInAnyOrder("service", "http.route")
}

@TableTest(
"""
scenario | overlay | message
undeclared name | 'intercepted: [missing]' | is not declared
ref in overlay | 'tags: [{ref: service}]' | must be declarations, not refs
unknown section | 'span_types: {}' | may only declare
unknown section | 'trace_level: {}' | may only declare
duplicate of a tag | 'tags: [{dd-name: service}]' | declared in both
mixin with no applies | 'mixins: {servlet: {tags: [{dd-name: servlet.path}]}}' | must say which span types
mixin reusing a shared name | 'mixins: {peer: {applies: [base], tags: [{dd-name: x.y}]}}' | reuses the name
type with no extends | 'span_types: {servlet.request: {tags: [{dd-name: servlet.path}]}}' | must extend a span type
type reusing a shared name | 'span_types: {base: {extends: base}}' | reuses the name of a shared span type
"""
)
fun `invalid tracer overlays are rejected`(overlay: String, message: String) {
val yaml = directory.conventionsFile("span_types: {base: {tags: [{dd-name: service}]}}")
val yaml =
directory.conventionsFile(
"span_types: {base: {tags: [{dd-name: service}]}}\nmixins: {peer: {tags: [{dd-name: peer.service}]}}"
)
val overlayFile = directory.writeFile("overlay.yaml", overlay)

assertThatIllegalArgumentException()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ public class InstrumentationTags {
public static final String SERVLET_PATH = "servlet.path";
public static final String SERVLET_DISPATCH = "servlet.dispatch";
public static final String TIMEOUT = "timeout";
public static final String JMS_PRODUCE = "jms.produce";
public static final String JSP_COMPILER = "jsp.compiler";
public static final String JSP_CLASSFQCN = "jsp.classFQCN";
public static final String JSP_FORWARD_ORIGIN = "jsp.forwardOrigin";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ public class Tags {
public static final String DB_OPERATION = "db.operation";
public static final String DB_STATEMENT = "db.statement";
public static final String DB_WAREHOUSE = "db.warehouse";
public static final String DB_HOST = "db.host";
public static final String DB_SCHEMA = "db.schema";
public static final String MESSAGE_BUS_DESTINATION = "message_bus.destination";
public static final String DB_POOL_NAME = "db.pool.name";
Expand Down
Loading
Loading