Upcoming Webinar:

Getting Started with OpenObserve

August 13, 2026
11:00 AM ET

Ready to get started?

Try OpenObserve Cloud today for more efficient and performant observability.

Table of Contents
Diagram showing OpenTelemetry semantic conventions standardizing attribute names across resources, spans, metrics, and logs

TL;DR

OpenTelemetry semantic conventions are the standardized names and formats for telemetry attributes, so service.name, http.request.method, and db.system.name mean the same thing no matter what language, library, or vendor produced them. Without this standard, every team invents its own field names, and dashboards break the moment a new service or library enters the mix. Attributes live in one of four places, resource, span, metric, or log, each with its own conventions, and every attribute carries a stability label (Development, Release Candidate, or Stable) that tells you whether it's safe to build a permanent dashboard on top of it. This guide covers what they are, where to find them, real current attribute names for HTTP and databases, the naming rules for your own custom attributes, and where Gen AI conventions fit in.

What Are OpenTelemetry Semantic Conventions?

OpenTelemetry semantic conventions are a specification that defines standard names, and the expected format of the values that go with them, for the attributes attached to telemetry data. They cover four kinds of telemetry: resources, spans, metrics, and logs.

Concretely, that means the spec says: the HTTP method on a span is named http.request.method, and its value looks like GET or POST. Not httpMethod, not http_verb, not method, not lowercase get. Every OpenTelemetry-instrumented HTTP library, in every language, that follows the spec produces that exact attribute name and format.

What semantic conventions are not: a storage format, a query language, or something specific to one backend. They're a naming and formatting agreement that sits underneath OpenTelemetry's traces, metrics, and logs, independent of which backend you send data to.

Why Semantic Conventions Matter

Here's the problem they solve, concretely. Say your checkout service is instrumented by one engineer and your inventory service by another. Without a shared standard, one might record the HTTP method as http.method, the other as httpVerb. Both are reasonable names. Neither is wrong. But now:

  • A dashboard filtering on http.method = "POST" silently misses every span from the inventory service
  • An alert rule written against one naming scheme doesn't fire for services using the other
  • Switching an HTTP client library, or a whole language, can rename the field under you

Semantic conventions remove the naming decision entirely. You don't choose how to name the HTTP method attribute; the spec already decided, and every compliant library follows it. That's also what makes auto-instrumentation actually useful: a library that emits spec-compliant spans produces telemetry your backend already knows how to turn into a service map, a latency percentile panel, or an error-rate dashboard, with no manual field mapping on your end.

This is also the property that makes switching observability backends realistic. If your telemetry already follows the spec, moving from one OTLP-compatible backend to another is a collector endpoint change, not a re-instrumentation project, because the data means the same thing on either end.

Where Semantic Conventions Apply: Four Kinds of Attributes

Semantic conventions aren't one flat list, they're organized by what the attribute is attached to:

  1. Resource attributes: describe the entity producing telemetry (a service, a host, a container). Set once, apply to everything that entity emits.
  2. Span attributes: describe a single operation within a trace (an HTTP request, a database query).
  3. Metric conventions: standardize metric names, instrument types, and units (for example, http.server.request.duration as a histogram in seconds).
  4. Log attributes: standardize fields like severity and body structure for log records.

The rest of this guide focuses mainly on resource and span attributes, since that's where most beginners start.

Resource Attributes: Identifying Where Telemetry Came From

A resource is the thing emitting telemetry, your service, running on a host, inside a container, in a specific environment. Resource attributes are set once when you configure your SDK and get attached to every span, metric, and log that service produces.

The one resource attribute that's actually required is service.name. Everything else is recommended, but skipping it is the single most common beginner mistake, an unnamed service shows up in your backend as unknown_service, and every dashboard built around "which service is this" breaks for it.

Common resource attributes you'll set or see:

Attribute Meaning Example
service.name Logical name of the service (required) checkout-api
service.version Version of the service 2.4.1
service.instance.id Unique ID for this running instance i-0a1b2c3d
deployment.environment.name Which environment this is production
host.name Hostname of the machine ip-10-0-1-42
cloud.provider Cloud vendor aws
k8s.pod.name / k8s.namespace.name Kubernetes identifiers checkout-7f9b, payments
telemetry.sdk.name Set automatically by the SDK opentelemetry

service.name is typically set through an environment variable (OTEL_SERVICE_NAME) or the OTEL_RESOURCE_ATTRIBUTES variable, not hardcoded per call, since it doesn't change for the life of the process.

Span Attributes: Describing a Single Operation

Where resource attributes answer "who produced this," span attributes answer "what happened in this specific operation." Every domain, HTTP, databases, messaging queues, RPC calls, has its own namespace of span attributes defined in the spec:

  • http.* and url.*, server.* for web requests
  • db.* for database calls
  • messaging.* for queues and event streams (Kafka, RabbitMQ, SQS)
  • rpc.* for gRPC and other RPC frameworks
  • faas.* for serverless function invocations
  • exception.* for errors captured on a span
  • code.* for source location (function name, file, line)

You rarely write these by hand. Auto-instrumentation libraries populate them for you, that's the entire point. But knowing the namespace and the current attribute names matters the moment you write a manual span, a query, or a dashboard panel.

HTTP Semantic Conventions: A Concrete Example

HTTP is the namespace nearly every beginner hits first, and it's also the one where the spec changed names significantly during stabilization, so older blog posts and tutorials often show attribute names that no longer match what's actually stable today.

Current, stable attribute names for an HTTP server span:

Attribute Meaning Requirement
http.request.method HTTP verb (GET, POST, ...) Required
url.path Path component of the URL Required
url.scheme http or https Required
http.route Matched route template (e.g. /users/{id}) Required if available
http.response.status_code Response status code Required if a response was sent
server.address Domain or IP the request targeted Recommended
client.address Address the request came from Recommended
user_agent.original Raw User-Agent header Recommended
error.type Set when the request ended in an error Required if errored

HTTP Semantic Conventions as they appear in an OpenObserve span

Note: the spec's dotted names (http.request.method, http.route) are what's shown above conceptually, but OpenObserve flattens dotted attribute names to underscores at ingest, so in the UI and in SQL you'll actually query these as http_request_method, http_route, and so on, not with the dots. Same attribute, same meaning, just the queryable form.

If you've seen older material referencing http.method, http.status_code, or net.peer.name, those are the pre-stabilization names. The spec renamed them (http.methodhttp.request.method, http.status_codehttp.response.status_code, net.peer.nameserver.address) as part of getting the HTTP conventions to Stable. If you're on an older instrumentation library version, you may still see the old names, which is exactly why checking an attribute's stability, covered below, matters before you build something permanent on top of it.

Database Semantic Conventions: A Concrete Example

Database spans follow the same idea, standardized names regardless of whether you're querying Postgres, MongoDB, or Redis:

Attribute Meaning Requirement
db.system.name The database product (postgresql, mongodb, mysql) Required
db.namespace Database or schema name Required if available
db.collection.name Table or collection name Required if available
db.operation.name The operation (SELECT, findAndModify) Required if available
db.query.text The actual query text Recommended, opt-in
db.query.summary Low-cardinality summary for grouping similar queries Recommended
db.response.status_code Set if the operation failed Required if errored

Database Semantic Conventions as they appear in an OpenObserve span

Note: same flattening applies here, db.system.name, db.namespace, db.query.summary are the spec's dotted names; in OpenObserve you query them as db_system_name, db_namespace, db_query_summary.

Two things worth flagging for beginners specifically. First, db.query.text is opt-in for a reason: raw query text can carry sensitive literal values (a user's email in a WHERE clause), so instrumentation libraries typically require you to explicitly enable it rather than capturing it by default, sanitize or scrub it if you turn it on. Second, db.query.summary exists precisely so you can group SELECT * FROM orders WHERE id = ? style patterns without needing the full text, useful for a "slowest query shapes" dashboard panel without touching potentially sensitive data at all.

Attribute Naming Rules (For Your Own Custom Attributes)

The spec doesn't cover everything, your order.id, your tenant.id, your internal feature flag name all need custom attributes. Follow the same rules the spec itself uses:

  • Lowercase, dot-separated namespacing: checkout.cart_id, not CheckoutCartId or cart-id
  • snake_case within each segment: payment.retry_count, not payment.retryCount
  • Namespace with something that won't collide: prefix with your company or product name if the concept could plausibly be added to the spec later (acme.order.id), rather than a bare order.id
  • Never reuse a registered namespace for a different meaning: don't put a custom value under http.*, db.*, or service.*, that's what breaks dashboards and queries built against the actual spec

The underlying rule is simple: your custom attributes should be unambiguous next to a standard one, never mistakable for it.

Stability Levels: Development, Release Candidate, Stable

Every attribute in the spec carries a stability label, and it matters more than beginners usually expect:

  • Development: still being refined. Names, value formats, or the attribute's existence can change without a deprecation path. Fine to explore, risky to build a permanent alert or dashboard on.
  • Release Candidate: close to final, unlikely to change further, but not yet locked.
  • Stable: locked in. A stable attribute won't be silently renamed or removed; if the underlying model changes, it gets formally deprecated with a replacement, not just swapped out.

Naming history worth knowing: "Development" was called "Experimental" in earlier versions of the spec. They mean the same thing, so if you see "Experimental" in older docs, a tutorial, or in SDK output, treat it exactly like "Development," not as some separate, murkier tier.

Practically: before you write a dashboard panel or alert rule against an attribute you're not sure about, check its stability in the spec. An attribute in Development (or still labeled Experimental) that gets renamed six months later means quietly broken dashboards until someone notices.

Versioning across the whole spec is tracked with a schema URL (e.g., a versioned link resolving to a specific spec release), which tools can use to understand which version of the conventions a given piece of telemetry was produced under, and to programmatically translate old attribute names to new ones when the spec evolves.

Semantic Conventions for Gen AI and LLMs

If you're instrumenting LLM calls or AI agents, there's a dedicated namespace for that too: gen_ai.*. It covers the same idea, standardizing telemetry, applied to a newer kind of workload:

  • gen_ai.provider.name: which provider handled the call (openai, anthropic)
  • gen_ai.request.model / gen_ai.response.model: requested vs. actually-served model
  • gen_ai.operation.name: the kind of operation (chat, text_completion)
  • gen_ai.usage.input_tokens / gen_ai.usage.output_tokens: token counts for cost attribution

Semantic Conventions for Gen AI and LLMs as they appear in an OpenObserve span

Note: consistent with the pattern above, these show up in OpenObserve as gen_ai_provider_name, gen_ai_request_model, gen_ai_usage_input_tokens, and so on, the dotted names below are the OTel spec form.

This is exactly the same motivation as HTTP or database conventions: a chat call instrumented in Python and one instrumented in TypeScript should produce spans a cost-per-model dashboard can read identically. For the deeper walkthrough of instrumenting LLM calls with these attributes, see OpenTelemetry for LLMs.

Common Beginner Mistakes

Inventing a name the spec already defines. Before adding a custom attribute, check the semantic conventions registry for something that already covers it. A custom http_status field next to the standard http.response.status_code is redundant and confuses anyone querying your data later.

Skipping service.name. It's the one resource attribute that's actually required. Skip it and every unnamed service shows up as unknown_service in your backend.

Capturing sensitive data in opt-in fields without sanitizing. db.query.text and similar fields are opt-in specifically because they can carry PII or secrets in literal values. Turning them on without a sanitization step ships that data straight into your observability backend.

Mixing old and new attribute names across services. If some of your services run older instrumentation library versions still emitting pre-stabilization names (http.method instead of http.request.method), your dashboards and queries need to account for both until everything's upgraded, or you'll silently undercount.

Reusing a spec namespace for something custom. Don't put your own meaning under http.* or db.*. Namespace custom attributes separately so there's no ambiguity between "spec-defined" and "something we made up."

Quick Reference: Common Attributes by Namespace

Namespace Applies to Example attributes
Resource The whole service/process service.name, deployment.environment.name, host.name
http.* / url.* / server.* Web requests http.request.method, http.route, url.path
db.* Database calls db.system.name, db.namespace, db.query.summary
messaging.* Queues and streams messaging.system, messaging.destination.name
rpc.* RPC/gRPC calls rpc.system, rpc.method
exception.* Errors on a span exception.type, exception.message
gen_ai.* LLM/AI calls gen_ai.provider.name, gen_ai.usage.input_tokens

How OpenObserve Uses Semantic Conventions

Because OpenObserve is OpenTelemetry-native, it ingests OTLP data without translating attribute names into a proprietary schema, so semantic conventions carry straight through: a service.name or http.route from your instrumentation is queryable by that exact name, with automatic field discovery surfacing it without a manual schema step. That's what makes prebuilt service maps, latency panels, and correlation between traces, logs, and metrics work out of the box once your telemetry follows the spec, no separate mapping layer between what your code emits and what shows up in a dashboard.

How OpenObserve Uses Semantic Conventions for automatic field discovery surfacing without a manual schema step.

Conclusion

Semantic conventions are the reason OpenTelemetry data is portable at all: a shared vocabulary so service.name, http.request.method, and db.system.name mean the same thing regardless of language, library, or backend. Start by getting service.name set correctly, let auto-instrumentation populate the rest, check an attribute's stability before building something permanent on it, and namespace your own custom attributes so they never collide with the spec. The full, current reference lives at the OpenTelemetry Semantic Conventions specification, worth bookmarking rather than memorizing.

Start a Free Trial of OpenObserve to see OpenTelemetry-native ingestion in practice, or read What Is OpenTelemetry? if you're starting from the very beginning.

Further reading:

Frequently Asked Questions

About the Author

Simran Kumari

Simran Kumari

LinkedIn

Passionate about observability, AI systems, and cloud-native tools. All in on DevOps and improving the developer experience.

Latest From Our Blogs

View all posts