OpenTelemetry Semantic Conventions - Standard Attributes for Traces, Metrics & Logs
OpenTelemetry semantic conventions are standardized attribute names and values that provide a common vocabulary for describing telemetry data across different languages, frameworks, and observability backends. By using semantic conventions, you ensure your traces, metrics, and logs are consistent, portable, and easily understood by both humans and tools.
Semantic conventions eliminate ambiguity in telemetry by defining:
- Attribute names: Standardized keys like
http.method,db.system,messaging.destination - Attribute values: Expected value formats like
GETfor HTTP methods,postgresqlfor databases - Required vs optional attributes: Which attributes must be present and which are recommended
- Naming patterns: Consistent hierarchical naming like
service.name,service.version,service.namespace
Why Semantic Conventions Matter
Without semantic conventions, different teams might represent the same concept differently:
Without conventions ❌:
http_method="GET"
http.verb="get"
request_method="GET"
method="GET"
With conventions ✅:
http.method="GET"
This standardization enables:
- Cross-language consistency: Java and Python services use identical attribute names
- Backend compatibility: APM tools can parse and visualize data predictably
- Query portability: Queries work across different services and languages
- Better dashboards: Pre-built dashboards work without customization
- Correlation: Easily correlate traces, metrics, and logs using standard attributes
Attribute Types
Semantic conventions define two main categories of attributes:
Resource Attributes
Resource attributes describe the entity producing telemetry (service, host, container). They're set once at initialization and apply to all telemetry from that source:
| Attribute | Description | Example |
|---|---|---|
service.name | Logical service name | checkout-api |
service.version | Service version | 1.2.3 |
service.namespace | Service namespace | production |
service.instance.id | Unique instance identifier | checkout-api-7f8b9c-abc123 |
deployment.environment | Deployment environment | production, staging, dev |
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 |
import (
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("checkout-api"),
semconv.ServiceVersion("1.2.3"),
semconv.DeploymentEnvironment("production"),
),
)
Span Attributes
Span attributes describe specific operations within traces. They vary per span and provide context about what happened:
import (
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)
span.SetAttributes(
semconv.HTTPMethod("GET"),
semconv.HTTPRoute("/api/users/:id"),
semconv.HTTPStatusCode(200),
semconv.HTTPTarget("/api/users/12345"),
)
HTTP Semantic Conventions
HTTP is one of the most common protocols, with well-defined semantic conventions:
HTTP Client Spans
When your service makes an HTTP request to another service:
| Attribute | Required | Description | Example |
|---|---|---|---|
http.method | ✅ Yes | HTTP method | GET, POST, PUT, DELETE |
http.url | ✅ Yes | Full URL | https://api.example.com/users/123 |
http.status_code | ✅ Yes | HTTP response status | 200, 404, 500 |
http.scheme | No | Protocol scheme | http, https |
http.target | No | Request target | /users/123?page=1 |
http.host | No | Host header value | api.example.com |
http.user_agent | No | User agent string | Mozilla/5.0... |
http.request_content_length | No | Request body size | 1024 |
http.response_content_length | No | Response body size | 2048 |
ctx, span := tracer.Start(ctx, "HTTP GET",
trace.WithSpanKind(trace.SpanKindClient),
)
defer span.End()
span.SetAttributes(
semconv.HTTPMethod("GET"),
semconv.HTTPURLKey.String("https://api.example.com/users/123"),
semconv.HTTPScheme("https"),
semconv.HTTPTarget("/users/123"),
)
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.HTTPStatusCode(resp.StatusCode),
)
HTTP Server Spans
When your service receives an HTTP request:
| Attribute | Required | Description | Example |
|---|---|---|---|
http.method | ✅ Yes | HTTP method | GET, POST |
http.route | ✅ Yes | Matched route pattern | /api/users/:id |
http.status_code | ✅ Yes | Response status | 200, 404 |
http.target | No | Request target | /api/users/123 |
http.client_ip | No | Client IP address | 192.168.1.100 |
Important: Use http.route (not http.target) for server spans to avoid high cardinality. Routes like /api/users/:id group similar requests together, while targets like /api/users/123, /api/users/456 create unique values for each request.
Database Semantic Conventions
Database operations have specific conventions for spans and metrics:
| Attribute | Required | Description | Example |
|---|---|---|---|
db.system | ✅ Yes | Database system | postgresql, mysql, mongodb, redis |
db.name | ✅ Yes | Database name | users_db, inventory |
db.operation | ✅ Yes | Operation type | SELECT, INSERT, UPDATE, DELETE, findOne |
db.statement | No | Database statement | SELECT * FROM users WHERE id = ? |
db.connection_string | No | Connection string (no credentials) | postgresql://db.example.com:5432/users_db |
db.user | No | Database username | app_user |
db.sql.table | No | Primary table name | users |
ctx, span := tracer.Start(ctx, "query users",
trace.WithSpanKind(trace.SpanKindClient),
)
defer span.End()
span.SetAttributes(
semconv.DBSystemPostgreSQL,
semconv.DBName("users_db"),
semconv.DBOperation("SELECT"),
semconv.DBStatement("SELECT * FROM users WHERE id = $1"),
semconv.DBSQLTable("users"),
)
rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE id = $1", userID)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
}
Database-Specific Conventions
SQL Databases (postgresql, mysql, mssql, oracle):
- Use
db.sql.tablefor the primary table - Sanitize
db.statementto remove sensitive data - Include
db.userfor access auditing
NoSQL Databases (mongodb, cassandra, dynamodb):
- Use
db.mongodb.collectionfor MongoDB collections - Use
db.cassandra.keyspacefor Cassandra keyspaces - Include operation-specific attributes like
db.mongodb.document
Key-Value Stores (redis, memcached):
- Keep
db.statementconcise (command + key, not full value) - Use
db.operationfor the command type
RPC and gRPC Semantic Conventions
For remote procedure calls and gRPC:
| Attribute | Required | Description | Example |
|---|---|---|---|
rpc.system | ✅ Yes | RPC system | grpc, java_rmi, wcf |
rpc.service | ✅ Yes | Service name | myservice.EchoService |
rpc.method | ✅ Yes | Method name | Echo |
rpc.grpc.status_code | No | gRPC status code | 0 (OK), 2 (UNKNOWN) |
ctx, span := tracer.Start(ctx, "grpc.myservice.EchoService/Echo",
trace.WithSpanKind(trace.SpanKindClient),
)
defer span.End()
span.SetAttributes(
semconv.RPCSystem("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)
}
Messaging Semantic Conventions
For message queues and pub/sub systems:
| Attribute | Required | Description | Example |
|---|---|---|---|
messaging.system | ✅ Yes | Messaging system | kafka, rabbitmq, aws_sqs, google_pubsub |
messaging.destination | ✅ Yes | Destination name | orders-topic, notifications-queue |
messaging.operation | ✅ Yes | Operation type | publish, receive, process |
messaging.message_id | No | Message identifier | msg-123456 |
messaging.conversation_id | No | Conversation/correlation ID | order-789 |
ctx, span := tracer.Start(ctx, "send to orders-topic",
trace.WithSpanKind(trace.SpanKindProducer),
)
defer span.End()
span.SetAttributes(
semconv.MessagingSystem("kafka"),
semconv.MessagingDestination("orders-topic"),
semconv.MessagingOperation("publish"),
attribute.String("messaging.kafka.partition", "0"),
)
err := producer.SendMessage(&sarama.ProducerMessage{
Topic: "orders-topic",
Value: sarama.StringEncoder(message),
})
Naming Conventions
Semantic conventions follow specific naming rules:
Namespace Hierarchy
Attributes use dot notation to create namespaces:
service.name
service.version
service.instance.id
http.method
http.status_code
http.request_content_length
db.system
db.name
db.operation
Naming Rules
- Use lowercase with underscores:
http.status_codenothttp.statusCode - Use dots for namespaces:
db.connection_stringnotdb_connection_string - Be specific but concise:
http.request_content_lengthnothttp.request.content.length - Use singular nouns:
http.methodnothttp.methods - Avoid abbreviations:
http.user_agentnothttp.ua(unless widely recognized)
Value Formatting
- Enumerations: Use lowercase strings:
GET,POST,grpc,kafka - Numbers: Use appropriate types: integers for counts, floats for durations
- Booleans: Use
true/false, not1/0or strings - URLs: Use full URLs:
https://api.example.com/usersnotapi.example.com/users
Cardinality Considerations
High cardinality attributes can cause performance and storage issues:
Low cardinality ✅ (Good for attributes):
http.method: Limited values (GET,POST, etc.)http.status_code: ~60 standard codesdb.system: Finite database typeshttp.route: Fixed route patterns
High cardinality ❌ (Avoid in attributes):
http.target: Every unique URL (/users/1,/users/2, ...)user.id: One value per usertransaction.id: Unique per transactionip.address: Many unique IPs
Use high cardinality data in span events or logs, not as span attributes.
Language-Specific Implementations
Each language provides semantic conventions through a package:
Go: go.opentelemetry.io/otel/semconv/v1.21.0
Python: opentelemetry-semantic-conventions
JavaScript/Node.js: @opentelemetry/semantic-conventions
Java: io.opentelemetry:opentelemetry-semconv
Ruby: opentelemetry-semantic_conventions
.NET: OpenTelemetry.SemanticConventions
PHP: open-telemetry/sem-conv
Install the package and import semantic attributes:
go get go.opentelemetry.io/otel/semconv/v1.21.0
Version Compatibility
Semantic conventions evolve over time. When using them:
- Pin specific versions in production to avoid breaking changes
- Check compatibility between SDK and semantic conventions versions
- Update gradually when new conventions are released
- Monitor deprecations for attributes being phased out
Version format: v{major}.{minor}.{patch} (e.g., v1.21.0)
Best Practices
- Always use semantic conventions when available: Don't create custom attributes for common operations
- Set required attributes first: Ensure critical attributes are present before optional ones
- Keep values consistent: Use the same format across all services (e.g., always lowercase for enums)
- Document custom attributes: When semantic conventions don't cover your use case, document your custom attributes
- Validate in development: Check that attributes match conventions during testing
- Use constants, not strings: Import semantic convention packages to avoid typos
- Consider cardinality: Limit attribute values to reasonable sets
- Sanitize sensitive data: Remove credentials, tokens, and PII from attributes
Custom Attributes
When semantic conventions don't cover your needs, create custom attributes following the same patterns:
app.feature.action
mycompany.service.custom_metric
internal.cache.hit_ratio
Guidelines for custom attributes:
- Use your organization/app name as a prefix
- Follow the same naming conventions
- Document them for your team
- Consider contributing them to OpenTelemetry if broadly applicable
Common Pitfalls
Using high-cardinality values ❌:
# DON'T: user_id creates millions of unique values
span.set_attribute("user.id", user_id)
Inconsistent formatting ❌:
# DON'T: Mix formats
span.set_attribute("http.method", "get") # Should be "GET"
span.set_attribute("http.method", "Post") # Should be "POST"
Custom attributes without namespaces ❌:
# DON'T: No namespace
span.set_attribute("cache_hit", True)
# DO: Use namespace
span.set_attribute("app.cache.hit", True)
Ignoring required attributes ❌:
# DON'T: Missing required http.method
span.set_attribute("http.status_code", 200)
# DO: Include all required attributes
span.set_attributes({
"http.method": "GET",
"http.url": url,
"http.status_code": 200,
})