Datadog → Berserk Field Mapping
Where Datadog's facets, tags and attributes land in Berserk, and how common Datadog queries translate to KQL
Data sent through the Datadog intake is stored as OpenTelemetry rows. This page is for people who know Datadog's UI and attribute names: it says where each Datadog concept ends up, and how to write the queries you are used to.
Two things to know first:
- Attribute names keep their Datadog spelling.
@http.status_codein Datadog isattributes['http.status_code']in Berserk. Nested objects are flattened to dotted keys, so either form of a Datadog attribute lands on the same key. - The reserved tags move to OpenTelemetry names.
service,env,versionandhostare stored on the row'sresourceunder OTel semantic-convention keys. Logs and metrics also keep them as attributes.
Unified Service Tagging
| Datadog | Berserk | Notes |
|---|---|---|
service | resource['service.name'] | On spans, per span |
env | resource['deployment.environment.name'] | |
version | resource['service.version'] | |
host | resource['host.name'] | From the log's hostname, the series' host resource, the sketch's host, or the tracer's hostname; a host: tag otherwise |
Other tags (team:payments) | attributes['team'] | A tag key repeated with different values (team:a,team:b) becomes an array |
Logs
| Datadog | Berserk |
|---|---|
| Message | body |
Status (error, warn, info, …) | severity_text (as sent) and severity_number (OTel scale: debug 5, info 9, warn 13, error 17, critical 18, alert 19, emergency 21) |
| Date | timestamp |
Source (ddsource) | attributes['ddsource'] |
@attribute / @nested.attribute | attributes['attribute'] / attributes['nested.attribute'] |
Trace correlation (dd.trace_id, dd.span_id) | attributes['dd.trace_id'], attributes['dd.span_id'] (decimal); and trace_id/span_id, which match the spans' for these tracer versions |
| Origin of the row | scope_name = "datadog" |
When the message is a JSON object, its fields become attributes, and message/msg/log, status/severity/level, the timestamp keys and dd.trace_id are remapped as Datadog's pipelines do. See How fields are mapped.
Metrics
| Datadog | Berserk |
|---|---|
Metric name (checkout.requests) | metric_name |
| Gauge | metric_type = "gauge", value |
| Count | metric_type = "sum", aggregation_temporality = "DELTA", value |
| Rate (e.g. DogStatsD counters) | metric_type = "sum" DELTA, value × interval (a count), attributes['datadog.metric_type'] = "rate" |
| Distribution | metric_type = "exponential_histogram" DELTA: count, sum, min, max exact; buckets re-binned (percentiles within ~3.3%) |
Histogram (|h) sub-metrics (.avg, .max, .count, .95percentile) | Separate gauge and sum series, as the Agent sends them |
| Tags | attributes, with the reserved ones also on resource |
| Unit | metric_unit |
APM
| Datadog | Berserk |
|---|---|
Resource (GET /pay) | span_name |
Operation name (http.request) | attributes['dd.operation_name'] |
Span type (web, sql, http) | attributes['dd.span_type'] |
| Service | resource['service.name'] |
| Trace ID | trace_id, 128-bit hex; and attributes['dd.trace_id'], the low 64 bits in decimal, as Datadog shows it |
| Span ID, parent ID | span_id, parent_span_id (hex); attributes['dd.span_id'] (decimal) |
| Duration | duration (also start_time, end_time) |
| Error | status_code = "ERROR" |
error.message, error.type, error.stack | status_description holds the message; all three stay in attributes |
span.kind | span_kind (INTERNAL when the span does not say) |
Integration (component, e.g. flask) | scope_name, with scope_version = tracer version |
otel.scope.name / otel.scope.version (OTel-instrumented spans sent through the Agent) | scope_name / scope_version, ahead of component |
Span tags and metrics (http.status_code, db.system, …) | attributes, verbatim |
| Sampling priority | attributes['datadog.sampling_priority'] |
| Tracer language and version | resource['telemetry.sdk.language'], resource['telemetry.sdk.version'] |
| Runtime ID | resource['service.instance.id'] |
| Span links, span events | links, events |
Datadog shows trace IDs in decimal. To look one up, use attributes['dd.trace_id']. It holds the decimal low 64 bits on spans and logs alike, whether the trace is 64- or 128-bit. Berserk's own trace views use trace_id, which links logs to spans for these tracer versions.
Query Translations
Replace my_table with the table your ingest token writes to.
| Datadog | Berserk KQL |
|---|---|
Logs: service:checkout status:error | my_table | where resource['service.name'] == "checkout" and severity_number >= 17 |
Logs: @http.status_code:>=500 | my_table | where toint(attributes['http.status_code']) >= 500 |
| Logs and spans of one trace | my_table | where attributes['dd.trace_id'] == "9532127138774266268" |
Metrics: avg:checkout.queue_depth{*} by {host} | my_table | where metric_name == "checkout.queue_depth" | summarize avg(todouble(value)) by host = tostring(resource['host.name']), bin(timestamp, 1m) |
Metrics: sum:checkout.requests{*}.as_count() | my_table | where metric_name == "checkout.requests" | summarize sum(todouble(value)) by bin(timestamp, 1m) |
Metrics: p99:checkout.latency{*} (distribution) | my_table | where metric_name == "checkout.latency" | summarize otel_histogram_percentile($raw, 99) by bin(timestamp, 1m) |
APM: p99 latency and errors per resource of service:checkout | my_table | where resource['service.name'] == "checkout" and isnotempty(span_name) | summarize p99 = percentile(duration, 99), errors = countif(status_code == "ERROR") by span_name |
severity_number >= 17 matches error and above; severity_text holds the status as the sender wrote it, in whatever case, so compare it with =~.