Berserk Docs

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_code in Datadog is attributes['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, version and host are stored on the row's resource under OTel semantic-convention keys. Logs and metrics also keep them as attributes.

Unified Service Tagging

DatadogBerserkNotes
serviceresource['service.name']On spans, per span
envresource['deployment.environment.name']
versionresource['service.version']
hostresource['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

DatadogBerserk
Messagebody
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)
Datetimestamp
Source (ddsource)attributes['ddsource']
@attribute / @nested.attributeattributes['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 rowscope_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

DatadogBerserk
Metric name (checkout.requests)metric_name
Gaugemetric_type = "gauge", value
Countmetric_type = "sum", aggregation_temporality = "DELTA", value
Rate (e.g. DogStatsD counters)metric_type = "sum" DELTA, value × interval (a count), attributes['datadog.metric_type'] = "rate"
Distributionmetric_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
Tagsattributes, with the reserved ones also on resource
Unitmetric_unit

APM

DatadogBerserk
Resource (GET /pay)span_name
Operation name (http.request)attributes['dd.operation_name']
Span type (web, sql, http)attributes['dd.span_type']
Serviceresource['service.name']
Trace IDtrace_id, 128-bit hex; and attributes['dd.trace_id'], the low 64 bits in decimal, as Datadog shows it
Span ID, parent IDspan_id, parent_span_id (hex); attributes['dd.span_id'] (decimal)
Durationduration (also start_time, end_time)
Errorstatus_code = "ERROR"
error.message, error.type, error.stackstatus_description holds the message; all three stay in attributes
span.kindspan_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 priorityattributes['datadog.sampling_priority']
Tracer language and versionresource['telemetry.sdk.language'], resource['telemetry.sdk.version']
Runtime IDresource['service.instance.id']
Span links, span eventslinks, 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.

DatadogBerserk KQL
Logs: service:checkout status:errormy_table | where resource['service.name'] == "checkout" and severity_number >= 17
Logs: @http.status_code:>=500my_table | where toint(attributes['http.status_code']) >= 500
Logs and spans of one tracemy_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:checkoutmy_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 =~.

On this page