OpenTelemetry Semantic Conventions: Stable Attribute Names
OpenTelemetry semantic conventions are standardized attribute names and values for describing telemetry. They define a shared vocabulary so that traces, metrics, and logs from different languages, frameworks, and libraries can be queried the same way.
Conventions specify four things:
- Attribute names — keys such as
http.request.method,db.system.name,server.address - Attribute values — expected formats, such as
GETfor an HTTP method orpostgresqlfor a database - Requirement levels — which attributes are Required, Conditionally Required, Recommended, or Opt-In
- Namespaces — hierarchical naming such as
service.name,service.version,service.instance.id
Without them, three services can describe the same HTTP request as http_method, http.verb, and request_method, and no dashboard or query works across all three.
Quick start
Most of the time you do not set convention attributes by hand. Instrumentation libraries emit them for you, and the only attributes you are responsible for are the ones describing your service.
Set service.name at minimum. Without it, your telemetry arrives as unknown_service.
import (
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
)
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("checkout-api"),
semconv.ServiceVersion("1.2.3"),
semconv.DeploymentEnvironmentName("production"),
),
)
The same values can be set without touching code, through OpenTelemetry environment variables:
export OTEL_SERVICE_NAME=checkout-api
export OTEL_RESOURCE_ATTRIBUTES=service.version=1.2.3,deployment.environment.name=production
Attribute names changed during stabilization
Before writing any instrumentation, know that HTTP and database attributes were renamed when those conventions became stable. Most tutorials, Stack Overflow answers, and older code still use the pre-stable names.
The pattern behind the renames: flat names became namespaced, and protocol-specific names were replaced by shared ones. http.host became server.address because the same attribute now describes any network peer, not only HTTP.
HTTP
| Old name | Current name |
|---|---|
http.method | http.request.method |
http.status_code | http.response.status_code |
http.url | url.full |
http.target | url.path and url.query |
http.scheme | url.scheme |
http.host | server.address |
http.flavor | network.protocol.version |
http.user_agent | user_agent.original |
http.client_ip | client.address |
http.request_content_length | http.request.body.size |
http.response_content_length | http.response.body.size |
Databases
| Old name | Current name |
|---|---|
db.system | db.system.name |
db.name | db.namespace |
db.operation | db.operation.name |
db.statement | db.query.text |
db.sql.table | db.collection.name |
db.connection_string | removed |
db.user | removed |
Resources
| Old name | Current name |
|---|---|
deployment.environment | deployment.environment.name |
If you cannot migrate everything at once, see Uptrace normalization below — it maps old names onto new ones at query time.
Resource attributes
Resource attributes describe the entity producing telemetry. They are set once at startup and attached to every span, metric, and log from that process.
| Attribute | Description | Example |
|---|---|---|
service.name | Logical service name | checkout-api |
service.version | Service version | 1.2.3 |
service.namespace | Service namespace | shop |
service.instance.id | Unique instance identifier | checkout-api-7f8b9c-abc123 |
deployment.environment.name | Deployment environment | production, staging |
host.name | Hostname | web-server-01 |
container.name | Container name | checkout-api-container |
k8s.pod.name | Kubernetes pod name | checkout-api-7f8b9c-abc123 |
cloud.provider | Cloud provider | aws, gcp, azure |
cloud.region | Cloud region | us-east-1 |
service.name is the only required one. Pick a name that stays stable across deployments — it is the primary key for grouping telemetry in every backend.
HTTP
HTTP conventions are stable. Instrumentation libraries set these automatically; the tables below are for reading telemetry and for writing your own instrumentation.
Server spans
| Attribute | Requirement | Example |
|---|---|---|
http.request.method | Required | GET, POST |
url.path | Required | /api/users/12345 |
url.scheme | Required | https |
http.response.status_code | Conditionally Required | 200, 404, 500 |
http.route | Conditionally Required | /api/users/:id |
server.address | Recommended | api.example.com |
server.port | Conditionally Required | 443 |
client.address | Recommended | 192.168.1.100 |
user_agent.original | Recommended | Mozilla/5.0... |
network.protocol.version | Recommended | 1.1, 2 |
Use http.route for the span name and for grouping, not url.path. A route such as /api/users/:id has a bounded set of values, while url.path produces a new value for every user id. See cardinality below.
import (
"go.opentelemetry.io/otel/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
)
ctx, span := tracer.Start(ctx, "GET /api/users/:id",
trace.WithSpanKind(trace.SpanKindServer),
)
defer span.End()
span.SetAttributes(
semconv.HTTPRequestMethodKey.String("GET"),
semconv.HTTPRoute("/api/users/:id"),
semconv.URLPath("/api/users/12345"),
semconv.URLScheme("https"),
semconv.ServerAddress("api.example.com"),
)
span.SetAttributes(semconv.HTTPResponseStatusCode(200))
Client spans
| Attribute | Requirement | Example |
|---|---|---|
http.request.method | Required | GET, POST |
url.full | Required | https://api.example.com/users/123 |
server.address | Required | api.example.com |
server.port | Required | 443 |
http.response.status_code | Conditionally Required | 200, 404 |
network.protocol.version | Recommended | 1.1, 2 |
Client spans carry url.full rather than url.path, because the full target is what identifies an outgoing call. Strip credentials from the URL before recording it.
ctx, span := tracer.Start(ctx, "GET",
trace.WithSpanKind(trace.SpanKindClient),
)
defer span.End()
span.SetAttributes(
semconv.HTTPRequestMethodKey.String("GET"),
semconv.URLFull("https://api.example.com/users/123"),
semconv.ServerAddress("api.example.com"),
semconv.ServerPort(443),
)
resp, err := http.Get("https://api.example.com/users/123")
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return err
}
defer resp.Body.Close()
span.SetAttributes(semconv.HTTPResponseStatusCode(resp.StatusCode))
Databases
Database conventions are stable. Note that db.connection_string and db.user were removed rather than renamed — connection strings routinely carry credentials.
| Attribute | Requirement | Example |
|---|---|---|
db.system.name | Required | postgresql, mysql, mongodb, redis |
db.namespace | Conditionally Required | users_db |
db.operation.name | Conditionally Required | SELECT, INSERT, findOne |
db.collection.name | Conditionally Required | users |
db.query.text | Recommended | SELECT * FROM users WHERE id = ? |
server.address | Recommended | db.example.com |
server.port | Recommended | 5432 |
ctx, span := tracer.Start(ctx, "SELECT users",
trace.WithSpanKind(trace.SpanKindClient),
)
defer span.End()
span.SetAttributes(
semconv.DBSystemNameKey.String("postgresql"),
semconv.DBNamespace("users_db"),
semconv.DBOperationName("SELECT"),
semconv.DBCollectionName("users"),
semconv.DBQueryText("SELECT * FROM users WHERE id = $1"),
)
rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE id = $1", userID)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
}
Sanitize db.query.text before recording it. Replace literals with placeholders, as in the examples above — query text otherwise leaks user data into traces and inflates span size.
Messaging
| Attribute | Requirement | Example |
|---|---|---|
messaging.system | Required | kafka, rabbitmq, aws_sqs, gcp_pubsub |
messaging.operation.name | Required | send, receive, process |
messaging.operation.type | Conditionally Required | send, receive, process, settle |
messaging.destination.name | Conditionally Required | orders-topic |
messaging.message.id | Recommended | msg-123456 |
messaging.message.body.size | Recommended | 1024 |
ctx, span := tracer.Start(ctx, "send orders-topic",
trace.WithSpanKind(trace.SpanKindProducer),
)
defer span.End()
span.SetAttributes(
semconv.MessagingSystemKey.String("kafka"),
semconv.MessagingOperationName("send"),
semconv.MessagingDestinationName("orders-topic"),
)
err := producer.SendMessage(&sarama.ProducerMessage{
Topic: "orders-topic",
Value: sarama.StringEncoder(message),
})
Messaging spans only form a connected trace if the producer injects trace context into message headers and the consumer extracts it. See OpenTelemetry context propagation.
RPC and gRPC
RPC conventions are still in development, but the attribute names below have been stable in practice.
| Attribute | Requirement | Example |
|---|---|---|
rpc.system | Required | grpc, java_rmi, dotnet_wcf |
rpc.service | Recommended | myservice.EchoService |
rpc.method | Recommended | Echo |
rpc.grpc.status_code | Conditionally Required | 0 (OK), 2 (UNKNOWN) |
ctx, span := tracer.Start(ctx, "myservice.EchoService/Echo",
trace.WithSpanKind(trace.SpanKindClient),
)
defer span.End()
span.SetAttributes(
semconv.RPCSystemKey.String("grpc"),
semconv.RPCService("myservice.EchoService"),
semconv.RPCMethod("Echo"),
)
resp, err := client.Echo(ctx, req)
if err != nil {
if st, ok := status.FromError(err); ok {
span.SetAttributes(semconv.RPCGRPCStatusCodeKey.Int(int(st.Code())))
}
span.RecordError(err)
}
Generative AI
Attributes for LLM and agent instrumentation use the gen_ai.* namespace: gen_ai.system, gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, and others.
Two things matter when you look them up:
- They moved. As of semantic conventions v1.42.0, all
gen_ai.*attributes, metrics, events, and spans were deprecated in the main repository and now live in a dedicated GenAI conventions repository. Links intosemantic-conventions/docs/gen-aiare stale. - They are not stable. The GenAI conventions are in development and change faster than the core conventions, which is why they were split out. Pin what you depend on and expect renames.
For instrumenting LLM applications with these attributes, see OpenTelemetry for AI systems.
Naming rules
If no convention covers your case, follow the same rules the project uses.
- Namespace with dots:
app.cache.hit, notapp_cache_hit - Use
snake_caseinside a namespace segment:http.response.status_code - Prefer nesting over flat compound names:
http.request.body.size, nothttp.request_body_size - Use singular nouns:
http.request.method, nothttp.request.methods - Avoid abbreviations:
user_agent.original, notua.original - Namespace your own attributes with your company or application name, so they never collide with future conventions
Rule 3 is the one that changed. Early conventions used flat names such as http.request_content_length; the stable conventions nest them as http.request.body.size.
For values: enumerations are lowercase strings, booleans are true/false rather than 1/0, and durations are floating-point seconds.
Cardinality
Cardinality is the number of distinct values an attribute can take. Every distinct combination of attribute values on a metric creates a separate time series, so unbounded attributes are the main cause of runaway storage and slow queries.
Bounded, safe as attributes
http.request.method— a handful of valueshttp.response.status_code— around 60 valuesdb.system.name— a fixed listhttp.route— one per route in your application
Unbounded, keep off metrics
url.path— a new value per resource iduser.id,session.id— one per user or sessionclient.address— one per client
Unbounded values are acceptable on spans and logs, which are stored individually rather than aggregated. They are not acceptable as metric attributes. See cardinality management for how to drop or aggregate them.
Semantic convention packages
Each language ships generated constants so you do not type attribute names by hand. Using them means a rename shows up as a compile error rather than a silently broken dashboard.
| Language | Package |
|---|---|
| Go | go.opentelemetry.io/otel/semconv/vX.Y.Z |
| Python | opentelemetry-semantic-conventions |
| Node.js | @opentelemetry/semantic-conventions |
| Java | io.opentelemetry:opentelemetry-semconv |
| .NET | OpenTelemetry.SemanticConventions |
| Ruby | opentelemetry-semantic_conventions |
| PHP | open-telemetry/sem-conv |
go get go.opentelemetry.io/otel/semconv/v1.34.0
The Go package is versioned by import path: semconv/v1.34.0 and semconv/v1.30.0 can coexist in one build, which makes upgrades incremental. Other languages ship one version per package release.
Versioning and migration
Semantic conventions are versioned and released separately from the OpenTelemetry specification. Stable attributes do not change meaning within a major version; attributes still in development can be renamed in any release.
When upgrading:
- Read the changelog for renames before bumping the version
- Upgrade one signal at a time — resource attributes first, then spans, then metrics
- Update saved queries, dashboards, and alerts that reference renamed attributes
- Expect a transition period where both old and new names appear, because instrumentation libraries upgrade independently of your application code
Uptrace normalization
Uptrace applies normalization rules so that telemetry from sources on different convention versions can be queried together.
Attribute names are stored with dots replaced by underscores, for compatibility with tools such as Prometheus and Grafana Tempo: service.name becomes service_name.
You select a semantic convention version on the Project Settings page. Uptrace then renames incoming attributes to that version, so a service still emitting db_statement and a service emitting db_query_text both answer the same query. Existing rules are not modified, to avoid breaking queries that already work.
v1.33.0
| Normalized attr name | Candidates |
|---|---|
| deployment_environment_name | deployment_environment, environment, env |
| service_name | service, appname, application_name, fly_app_name |
| host_name | hostname, host_hostname, host |
| db_namespace | db_name, db_cassandra_keyspace, db_hbase_namespace, dbname |
| db_system_name | db_system, db_type, db_dbms |
| db_collection_name | db_sql_table |
| db_operation_name | db_operation |
| db_query_text | db_statement |
| http_request_method | http_method, request_method, method |
| http_route | http_server_route, route |
| log_severity | severity, error_severity, log_level, level |
| log_source | source_type, facility |
| log_file_path | log_filepath, log_file |
| code_function_name | code_function |
| code_file_path | code_filepath |
| code_line_number | code_lineno |
| process_pid | procid, pid |
| server_address | net_host_name, http_server_name, http_host |
| client_address | http_client_ip, net_peer_name, ip |
| messaging_message_id | message_id, msgid |
| grouping_fingerprint | log_fingerprint, exception_fingerprint |
v1.25.0
| Normalized attr name | Candidates |
|---|---|
| deployment_environment | deployment_environment_name, environment, env |
| service_name | service, appname, application_name, fly_app_name |
| host_name | hostname, host_hostname, host |
| enduser_id | user_id, user_identifier, user |
| db_name | db_cassandra_keyspace, db_hbase_namespace, dbname |
| db_system | db_type, db_dbms |
| http_request_method | http_method, request_method, method |
| http_route | http_server_route, route |
| http_response_status_code | http_status_code |
| http_request_body_size | http_request_content_length |
| http_response_body_size | http_response_content_length |
| log_severity | severity, error_severity, log_level, level |
| log_source | source_type, facility |
| log_file_path | log_filepath, log_file |
| cloud_region | fly_region |
| cloud_resource_id | faas_id |
| cloud_availability_zone | cloud_zone |
| process_pid | procid, pid |
| url_scheme | http_scheme |
| url_full | http_url |
| url_path | http_target, request |
| faas_invocation_id | faas_execution |
| user_agent_original | http_user_agent, browser_user_agent, user_agent |
| network_protocol_name | net_app_protocol_name, messaging_protocol, http_flavor |
| network_protocol_version | net_app_protocol_version, messaging_protocol_version |
| network_transport | net_transport |
| server_address | net_host_name, http_server_name, http_host |
| server_port | net_host_port |
| server_socket_domain | net_sock_peer_name |
| server_socket_address | net_host_ip, net_sock_host_addr |
| server_socket_port | net_host_port, net_sock_host_port |
| client_address | http_client_ip, net_peer_name, ip |
| client_socket_address | net_peer_ip, net_sock_peer_addr |
| client_socket_port | net_peer_port, net_sock_peer_port |
| messaging_destination_name | messaging_destination |
| messaging_destination_kind | messaging_destination_kind |
| messaging_message_id | message_id, msgid |
| messaging_message_type | message_type |
| messaging_message_payload_size_bytes | message_uncompressed_size |
| grouping_fingerprint | log_fingerprint, exception_fingerprint |
Indexed attributes
Uptrace stores data in ClickHouse, a columnar database. To speed up queries, some attributes are stored in dedicated columns instead of a generic attribute map. Attributes outside this list still work in queries, but filtering on an indexed attribute is faster.
| Attribute | Spans | Logs | Events | Preaggregated |
|---|---|---|---|---|
| deployment_environment | Yes | Yes | Yes | Yes |
| service_namespace | Yes | Yes | Yes | Yes |
| service_name | Yes | Yes | Yes | Yes |
| service_version | Yes | Yes | Yes | Yes |
| host_name | Yes | Yes | Yes | Yes |
| telemetry_sdk_name | Yes | Yes | ||
| telemetry_sdk_language | Yes | Yes | ||
| telemetry_sdk_version | Yes | Yes | ||
| telemetry_auto_version | Yes | Yes | ||
| otel_library_name | Yes | Yes | Yes | |
| otel_library_version | Yes | Yes | Yes | |
| client_address | Yes | |||
| client_socket_address | Yes | |||
| client_socket_port | Yes | |||
| db_system | Yes | |||
| db_name | Yes | |||
| db_sql_table | Yes | |||
| db_statement | Yes | |||
| db_operation | Yes | |||
| process_pid | Yes | Yes | ||
| process_command | Yes | Yes | ||
| process_runtime_name | Yes | Yes | ||
| process_runtime_version | Yes | Yes | ||
| process_runtime_description | Yes | Yes | ||
| log_severity | Yes | |||
| log_file_path | Yes | |||
| log_file_name | Yes | |||
| log_iostream | Yes | |||
| log_source | Yes | |||
| exception_type | Yes | |||
| exception_message | Yes | |||
| messaging_message_id | Yes | |||
| messaging_message_type | Yes | |||
| messaging_message_payload_size_bytes | Yes |
What's next?
- OpenTelemetry distributed tracing — spans, attributes, and span kinds
- OpenTelemetry metrics — instruments and cardinality management
- OpenTelemetry logs — log data model and trace correlation
- Get started with OpenTelemetry — SDK setup for your language
- OpenTelemetry Collector — renaming attributes in transit with OTTL
- OpenTelemetry architecture — resource attributes and where they are set