Last updated: June 9, 2025

OpenTelemetry Semantic Conventions: A Comprehensive Explainer

You might be collecting logs, metrics, and traces, but if every service calls the same thing by a different name, your telemetry quickly becomes a collection of isolated, hard-to-correlate data points.

When one service emits an HTTP status code as status_code and another emits it as http_status_code, and a third simply as code a fundamental disconnect occurs.

OpenTelemetry’s Semantic Conventions solve this by providing a single, industry-standard language for describing your system’s operations.

By adopting these conventions, telemetry becomes composable. Dashboards can be reused across services, alerts are standardized, and traces from different languages and frameworks line up perfectly.

This article provides a comprehensive explainer on what semantic conventions are, how they are structured for each signal, and how you can apply them effectively to transform your telemetry from just “data” into powerful insight.

Resource semantic conventions: where your telemetry is coming from

In OpenTelemetry, every piece of telemetry, whether a log, metric, or span, originates from a Resource. A Resource is the entity producing the data, be it a microservice, a serverless function, a Kubernetes pod, or a physical host.

The semantic conventions for resources standardize the attributes that describes this entity. The single most important one is service.name, which gives your application a logical identity in your observability platform. The most common way to set it is with an environment variable:

sh
1
export OTEL_SERVICE_NAME="payment-service"

While service.name is the most crucial identifier, a complete resource description answers several key questions: what is this service?, where is it running?, and how is it instrumented?

Let’s look at a few of the attributes that you can use to answer these questions.

Logical & deployment context

These attributes describe the service’s identity and its role in the software development lifecycle:

  • service.version: The version of the service (e.g. v1.2.3 or a git commit hash).
  • service.instance.id: A unique identifier for a specific instance.
  • service.namespace: A name that groups related services.
  • deployment.environment: The environment the service is deployed in (such as production or staging).
Service attributes in Dash0

Execution environment context

This group of attributes pinpoints the physical or virtual infrastructure where your code is running:

  • cloud.*: Describes the cloud provider, region, and platform.
  • aws.*, gcp.* (etc): Describes resources that are only applicable to a specific cloud provider.
  • k8s.*: For Kubernetes, this is a rich set of attributes describing the namespace, node, pod, cluster, containers, and more.
  • host.*, os.*, process.*: Describes the host machine, operating system, and the underlying OS processes.

A cross-section of some cloud resource attributes in Dash0

Telemetry origin context

The telemetry.sdk.* attributes describe the underlying technology that captured the telemetry, helping you understand how the data was generated.

Telemetry SDK attributes in OpenTelemetry Resources

How resource attributes are populated

A complete Resource object, like the OTLP/JSON example below, combines attributes gathered from multiple sources:

json
12345678910111213141516171819202122
{
"resource": {
"attributes": [
{ "key": "service.name", "value": { "stringValue": "payment-service" } },
{ "key": "service.version", "value": { "stringValue": "2.1.0" } },
{
"key": "deployment.environment",
"value": { "stringValue": "production" }
},
{
"key": "k8s.pod.uid",
"value": { "stringValue": "f4b0bf1b-de06-4261-804d-41efc30bba1e" }
},
{
"key": "telemetry.sdk.name",
"value": { "stringValue": "opentelemetry" }
},
{ "key": "telemetry.sdk.language", "value": { "stringValue": "go" } },
{ "key": "telemetry.sdk.version", "value": { "stringValue": "1.25.0" } }
]
}
}

You can populate these attributes in several ways (often combined together):

  • Your language’s SDK automatically adds attributes it can detect, like host.*, process.*, and telemetry.sdk.*.
  • The OTEL_RESOURCE_ATTRIBUTES environmental variable can be used to set any attribute:
sh
1
export OTEL_RESOURCE_ATTRIBUTES="service.version=1.2.3,deployment.environment=production"
yaml
1234578910111213141516
processors:
resourcedetection: # Adds infrastructure metadata
detectors: [env, gcp]
timeout: 2s
override: false
resource: # Manipulates resource attributes
attributes:
- key: cloud.availability_zone
value: zone-1
action: upsert
- key: k8s.cluster.name
from_attribute: k8s-cluster
action: insert
- key: redundant-attribute
action: delete

Trace semantic conventions: telling the story of a request

Trace conventions standardize the attributes on spans, which represent individual operations. This ensures that a trace tells a coherent story, regardless of which services or technologies are involved.

At the heart of this narrative, and beyond the unique identifiers that link spans together (traceId, spanId), are three fundamental semantic properties: its Name, its Kind, and its Status.

Spans properties in Dash0
  1. Span name: A concise low-cardinality identifier for the work represented by the span. For instance, the name for a span representing a request to an endpoint could be {method} {target} as in GET /product/{id} but not GET /product/123. High-cardinality details belong in span attributes.
  2. Span kind: This specifies the span’s role in an operation and contextualizes its relationship with parent and child spans. The valid kinds are:
    1. SERVER: Handling an incoming request.
    2. CLIENT: Making an outbound request.
    3. PRODUCER: Sending a message for asynchronous processing.
    4. CONSUMER: Processing a job initiated by a PRODUCER.
    5. INTERNAL (default): An operation that doesn’t cross service boundaries.
  3. Span status: This represents the outcome of the operation. By default, a span’s status is Unset. You should only set it when the outcome is known to either Ok (for successful operations), or Error to flag problematic traces.

Telling the story with attributes

OpenTelemetry trace span attributes in Dash0

Span attributes add the rich, contextual details to the story. For instance, when a service calls another via HTTP, two spans are created: a CLIENT span on the calling service and a SERVER span on the receiving service. Together, they tell a complete story of the network interaction.

The CLIENT span describes the outbound call with attributes like:

  • url.full: Where did I send the request?
  • http.request.method: What kind of request was it? (GET, POST, etc).
  • server.address: What was the logical name of the service I called?

On the other hand, the SERVER span describes the work it did through attributes like:

  • http.response.status_code: How did I respond?
  • client.address: Who made the request? (the IP address of the calling service)
  • url.path: What specific resource was requested? (e.g., /users/123)

This pairing is what brings a distributed trace to life. It allows observability tools to visualize the direct call between services and, crucially, to isolate the time spent “on the wire”—the latency between the CLIENT finishing its request and the SERVER beginning its work.

Conventions for database queries, messaging systems, and other operations follow similar principles, helping to create a complete and understandable view of every transaction.

Metric semantic conventions: best practices for instrumentation

Metric conventions provide the blueprint for creating reliable, intuitive, and interoperable measurements of your system’s behavior.

Their goal goes beyond just creating consistent dashboards; they enable you to build robust alerting, define meaningful Service Level Objectives (SLOs), and perform deep analytical queries that correlate directly with your trace and log data.

A well-defined metric in OpenTelemetry is built on four pillars: its Instrument type, its Name, its Unit, and its Attributes.

1. Instrument type

The instrument type defines the metric’s mathematical properties and what you can do with it. The available types are:

  • Counter: A value that only ever increases.
  • UpDownCounter: A value that can increase or decrease.
  • Gauge: A snapshot of a value at a specific point in time (such as current memory usage).
  • Histogram: A statistical distribution of measurements used to calculate averages, sums, and percentiles for things like request latency.
Duration of requests in Dash0 showing p99

2. Name

A metric’s name should immediately tell you what it is. For example, http.server.request.duration:

  • http: The general domain is networking over HTTP.
  • server: The perspective is that of a server handling requests.
  • request.duration: The specific measurement is the duration of requests.

This structure allows you to easily query for related metrics, such as http.server.*.

Beyond the basic structure, OpenTelemetry provides specific conventions for common metric patterns:

  • Well-known metric types should use a standard suffix (like .utilization for representing the fraction of a resource that is used).
  • Unlike Prometheus, metric names should not use the _total suffix. UpDownCounters may use count (e.g., system.process.count) to represent a count of items in a namespace.
  • For network metrics, indicate the perspective when it’s not obvious. For example, a metric name like http.request.duration is ambiguous since it doesn’t specify if it’s measuring the full round-trip time seen by the client or only the processing time handled by the server.

    To resolve this, the {area}.{client|server}.{metric_name} pattern is recommended such as: http.client.request.duration and http.server.request.duration.
Metric instruments in Dash0

3. Unit

Metric conventions remove unit ambiguity by requiring instruments to follow the Unified Code for Units of Measure (UCUM):

  • Time should be in seconds (s).
  • Data size should be in bytes (By).
  • Counts are dimensionless and annotated with braces to describe the thing being counted (e.g., {request}, {error}).

Standardizing units allows observability platforms to automatically handle conversions and lets you confidently compare metrics from different services.

4. Attributes (dimensions)

Attributes add dimensions to your metrics, allowing you to filter, group, and compare them.

For example, to identify which API endpoints are generating the most errors, you need to add a route attribute. However, using the raw URL path (like /users/123) would cause a cardinality explosion by creating a separate time series for every user ID.

The correct solution is to use a low-cardinality, parameterized route like /users/{id}, allowing you to group all errors for a specific endpoint together without ballooning your costs.

The metric conventions also define requirement levels for attributes included by the instrumentation library.

Log semantic conventions: a pragmatic approach

OpenTelemetry takes a pragmatic approach to logging as it does not aim to overhaul conventional logging practices using libraries like Log4j, Winston, or Serilog.

Instead, it provides a standard for structuring and enriching the output of these frameworks, transforming your logs from isolated text streams into a first-class signal for observability.

The goal is to answer the ultimate debugging question: “show me the exact logs, from all services, that were generated during this specific slow request”.

The OpenTelemetry log data model

To achieve this, OpenTelemetry defines a standard model for a log record. When a log is processed by an OTel SDK or Collector, it is mapped to these fields:

  • body: contains the original, primary content of the log message. For a simple text log, it’s the entire string itself. For modern structured logging, the log message often becomes the body.
  • timestamp: The Unix timestamp in nanoseconds.
  • severityText and severityNumber: These fields provide a standardized severity scale, mapping the varied terminology of different logging frameworks to a single enumeration.
  • Trace context: When logging occurs within the context of an active trace, the traceID, spanID, and traceFlags are automatically injected into the log record to enable direct correlation.
  • attributes: Contextual information from the original log is placed here to make it easily queryable.
OpenTelemetry log record in Dash0

Using the OpenTelemetry log bridge for your logging framework will take care of mapping the JSON output to the log data model.

Semantic conventions for logs come into play mostly in the attributes field for standardizing the vocabulary you use to add context to your logs.

While the semantic conventions for logs are still evolving, the most stable and widely-adopted rules focus on providing a standard structure for documenting exceptions:

  • exception.type: The type of the exception.
  • exception.message: The exception’s error message.
  • exception.stacktrace: The full stack trace as a string.

For other contextual attributes, the recommended practice is to apply the same general-purpose conventions used across traces and metrics.

Using shared keys like deployment.environment, host.name, or user.id ensures that you can filter and correlate all signals consistently.

When no standard attribute fits your needs, you can then define your own custom names.

Event semantic conventions: an evolving standard

In OpenTelemetry, an Event is a named occurrence at a specific moment in time, signaling that “this thing happened”.

Technically, events are implemented as a special type of log that follows a dedicated set of conventions. Every event is built on three main components:

  • event.name: A unique and required name that uniquely identifies the type of event (e.g. feature_flag.evaluation). This name is what differentiates an event from a conventional log record.
  • body: A payload containing data fields that are specific only to this event’s structure.
  • attributes: Standard attributes providing additional context about the event.

It’s important to note that the semantic conventions for events are an evolving standard. Over time, the OpenTelemetry project will define a library of standard events, providing official names, attributes, and payload structures for common use cases.

Custom attributes: adding your business context

While OpenTelemetry’s standard conventions cover a vast range of technical operations, the true power of observability is unlocked when you enrich your telemetry with business-specific context.

What is the shopping cart value for a failed request? Which customer tier is experiencing the highest latency? Answering these questions requires custom attributes.

However, adding custom attributes comes with the responsibility of careful design as a poorly designed one can create future conflicts and correlation issues.

Before you add any custom attribute, always check the official attribute registry first to see if a standard one already exists for your use case.

If you determine a custom attribute is needed, follow these four principles:

1. Choose a unique namespace

A namespace is a prefix that groups all your custom attributes and prevents them from ever colliding with standard attributes from OpenTelemetry or third-parties.

The convention is to use a reverse domain name, which is globally unique (such as com.example.product.id), but for internal applications where a full reverse domain is overkill, you can adopt a simpler prefix like myapp.customer.tier.

2. Use a consistent structure

Within your namespace, use lowercase, dot-separated names to create a logical and browsable hierarchy. This makes exploring your data and building queries much more intuitive.

12
app.shopping_cart.items
app.shopping_cart.value

3. Respect standard namespaces

Never add your own attributes within the standard OpenTelemetry namespaces (like http, db, or messaging).

4. Always consider cardinality

Before you add a new attribute, ask yourself: “How many unique values could this attribute have?” This is its cardinality.

  • Low cardinality: An attribute with a small, finite set of values (such as http.request.method).
  • High cardinality: An attribute with a very large or unbounded number of values (like product.id).

If an attribute has high cardinality, use it freely in logs and spans, but avoid adding it to metrics unless you have a specific, well-understood reason and your observability backend can support it.

Final thoughts

The power of semantic conventions is unlocked not just by adopting these conventions, but by applying them consistently across all signals.

When your logs, traces, and metrics all describe entities with the same name and semantics, you break down data silos and enable deep, cross-signal analysis. This is the foundation of a robust and effective observability strategy.

As you apply these principles, always treat the official OpenTelemetry documentation as the definitive source of truth for the latest conventions.

Authors
Ayooluwa Isaiah
Ayooluwa Isaiah