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 GET for HTTP methods, postgresql for 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 ❌:

text
http_method="GET"
http.verb="get"
request_method="GET"
method="GET"

With conventions ✅:

text
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:

AttributeDescriptionExample
service.nameLogical service namecheckout-api
service.versionService version1.2.3
service.namespaceService namespaceproduction
service.instance.idUnique instance identifiercheckout-api-7f8b9c-abc123
deployment.environmentDeployment environmentproduction, staging, dev
host.nameHostnameweb-server-01
container.nameContainer namecheckout-api-container
k8s.pod.nameKubernetes pod namecheckout-api-7f8b9c-abc123
cloud.providerCloud provideraws, gcp, azure
cloud.regionCloud regionus-east-1
go Go
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"),
    ),
)
python Python
from opentelemetry.sdk.resources import Resource
from opentelemetry.semconv.resource import ResourceAttributes

resource = Resource.create({
    ResourceAttributes.SERVICE_NAME: "checkout-api",
    ResourceAttributes.SERVICE_VERSION: "1.2.3",
    ResourceAttributes.DEPLOYMENT_ENVIRONMENT: "production",
})
javascript Node.js
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');

const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: 'checkout-api',
  [SemanticResourceAttributes.SERVICE_VERSION]: '1.2.3',
  [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: 'production',
});
java Java
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;

Resource resource = Resource.getDefault()
    .merge(Resource.create(Attributes.of(
        ResourceAttributes.SERVICE_NAME, "checkout-api",
        ResourceAttributes.SERVICE_VERSION, "1.2.3",
        ResourceAttributes.DEPLOYMENT_ENVIRONMENT, "production"
    )));

Span Attributes

Span attributes describe specific operations within traces. They vary per span and provide context about what happened:

go Go
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"),
)
python Python
from opentelemetry.semconv.trace import SpanAttributes

span.set_attributes({
    SpanAttributes.HTTP_METHOD: "GET",
    SpanAttributes.HTTP_ROUTE: "/api/users/:id",
    SpanAttributes.HTTP_STATUS_CODE: 200,
    SpanAttributes.HTTP_TARGET: "/api/users/12345",
})
javascript Node.js
const { SemanticAttributes } = require('@opentelemetry/semantic-conventions');

span.setAttributes({
  [SemanticAttributes.HTTP_METHOD]: 'GET',
  [SemanticAttributes.HTTP_ROUTE]: '/api/users/:id',
  [SemanticAttributes.HTTP_STATUS_CODE]: 200,
  [SemanticAttributes.HTTP_TARGET]: '/api/users/12345',
});
java Java
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;

span.setAllAttributes(Attributes.of(
    SemanticAttributes.HTTP_METHOD, "GET",
    SemanticAttributes.HTTP_ROUTE, "/api/users/:id",
    SemanticAttributes.HTTP_STATUS_CODE, 200L,
    SemanticAttributes.HTTP_TARGET, "/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:

AttributeRequiredDescriptionExample
http.method✅ YesHTTP methodGET, POST, PUT, DELETE
http.url✅ YesFull URLhttps://api.example.com/users/123
http.status_code✅ YesHTTP response status200, 404, 500
http.schemeNoProtocol schemehttp, https
http.targetNoRequest target/users/123?page=1
http.hostNoHost header valueapi.example.com
http.user_agentNoUser agent stringMozilla/5.0...
http.request_content_lengthNoRequest body size1024
http.response_content_lengthNoResponse body size2048
go Go
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),
)
python Python
from opentelemetry import trace
from opentelemetry.semconv.trace import SpanAttributes

tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span(
    "HTTP GET",
    kind=trace.SpanKind.CLIENT
) as span:
    span.set_attributes({
        SpanAttributes.HTTP_METHOD: "GET",
        SpanAttributes.HTTP_URL: "https://api.example.com/users/123",
        SpanAttributes.HTTP_SCHEME: "https",
        SpanAttributes.HTTP_TARGET: "/users/123",
    })

    response = requests.get("https://api.example.com/users/123")

    span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, response.status_code)
javascript Node.js
const { context, trace, SpanKind } = require('@opentelemetry/api');
const { SemanticAttributes } = require('@opentelemetry/semantic-conventions');

const span = tracer.startSpan('HTTP GET', {
  kind: SpanKind.CLIENT,
  attributes: {
    [SemanticAttributes.HTTP_METHOD]: 'GET',
    [SemanticAttributes.HTTP_URL]: 'https://api.example.com/users/123',
    [SemanticAttributes.HTTP_SCHEME]: 'https',
    [SemanticAttributes.HTTP_TARGET]: '/users/123',
  },
});

try {
  const response = await fetch('https://api.example.com/users/123');
  span.setAttribute(SemanticAttributes.HTTP_STATUS_CODE, response.status);
} finally {
  span.end();
}

HTTP Server Spans

When your service receives an HTTP request:

AttributeRequiredDescriptionExample
http.method✅ YesHTTP methodGET, POST
http.route✅ YesMatched route pattern/api/users/:id
http.status_code✅ YesResponse status200, 404
http.targetNoRequest target/api/users/123
http.client_ipNoClient IP address192.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:

AttributeRequiredDescriptionExample
db.system✅ YesDatabase systempostgresql, mysql, mongodb, redis
db.name✅ YesDatabase nameusers_db, inventory
db.operation✅ YesOperation typeSELECT, INSERT, UPDATE, DELETE, findOne
db.statementNoDatabase statementSELECT * FROM users WHERE id = ?
db.connection_stringNoConnection string (no credentials)postgresql://db.example.com:5432/users_db
db.userNoDatabase usernameapp_user
db.sql.tableNoPrimary table nameusers
go Go (PostgreSQL)
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())
}
python Python (MongoDB)
from opentelemetry import trace
from opentelemetry.semconv.trace import SpanAttributes

tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span(
    "find user",
    kind=trace.SpanKind.CLIENT
) as span:
    span.set_attributes({
        SpanAttributes.DB_SYSTEM: "mongodb",
        SpanAttributes.DB_NAME: "users_db",
        SpanAttributes.DB_OPERATION: "findOne",
        SpanAttributes.DB_STATEMENT: "db.users.findOne({id: '12345'})",
    })

    result = collection.find_one({"id": user_id})
javascript Node.js (Redis)
const { trace, SpanKind } = require('@opentelemetry/api');
const { SemanticAttributes } = require('@opentelemetry/semantic-conventions');

const span = tracer.startSpan('redis get', {
  kind: SpanKind.CLIENT,
  attributes: {
    [SemanticAttributes.DB_SYSTEM]: 'redis',
    [SemanticAttributes.DB_OPERATION]: 'GET',
    [SemanticAttributes.DB_STATEMENT]: 'GET user:12345',
  },
});

try {
  const value = await redis.get('user:12345');
} finally {
  span.end();
}

Database-Specific Conventions

SQL Databases (postgresql, mysql, mssql, oracle):

  • Use db.sql.table for the primary table
  • Sanitize db.statement to remove sensitive data
  • Include db.user for access auditing

NoSQL Databases (mongodb, cassandra, dynamodb):

  • Use db.mongodb.collection for MongoDB collections
  • Use db.cassandra.keyspace for Cassandra keyspaces
  • Include operation-specific attributes like db.mongodb.document

Key-Value Stores (redis, memcached):

  • Keep db.statement concise (command + key, not full value)
  • Use db.operation for the command type

RPC and gRPC Semantic Conventions

For remote procedure calls and gRPC:

AttributeRequiredDescriptionExample
rpc.system✅ YesRPC systemgrpc, java_rmi, wcf
rpc.service✅ YesService namemyservice.EchoService
rpc.method✅ YesMethod nameEcho
rpc.grpc.status_codeNogRPC status code0 (OK), 2 (UNKNOWN)
go Go (gRPC)
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)
}
python Python (gRPC)
from opentelemetry import trace
from opentelemetry.semconv.trace import SpanAttributes

with tracer.start_as_current_span(
    "grpc.myservice.EchoService/Echo",
    kind=trace.SpanKind.CLIENT
) as span:
    span.set_attributes({
        SpanAttributes.RPC_SYSTEM: "grpc",
        SpanAttributes.RPC_SERVICE: "myservice.EchoService",
        SpanAttributes.RPC_METHOD: "Echo",
    })

    try:
        response = stub.Echo(request)
    except grpc.RpcError as e:
        span.set_attribute(SpanAttributes.RPC_GRPC_STATUS_CODE, e.code().value[0])
        raise

Messaging Semantic Conventions

For message queues and pub/sub systems:

AttributeRequiredDescriptionExample
messaging.system✅ YesMessaging systemkafka, rabbitmq, aws_sqs, google_pubsub
messaging.destination✅ YesDestination nameorders-topic, notifications-queue
messaging.operation✅ YesOperation typepublish, receive, process
messaging.message_idNoMessage identifiermsg-123456
messaging.conversation_idNoConversation/correlation IDorder-789
go Go (Kafka Producer)
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),
})
python Python (RabbitMQ Consumer)
from opentelemetry import trace
from opentelemetry.semconv.trace import SpanAttributes

with tracer.start_as_current_span(
    "process from notifications-queue",
    kind=trace.SpanKind.CONSUMER
) as span:
    span.set_attributes({
        SpanAttributes.MESSAGING_SYSTEM: "rabbitmq",
        SpanAttributes.MESSAGING_DESTINATION: "notifications-queue",
        SpanAttributes.MESSAGING_OPERATION: "process",
        SpanAttributes.MESSAGING_MESSAGE_ID: message.message_id,
    })

    # Process message
    process_notification(message)

Naming Conventions

Semantic conventions follow specific naming rules:

Namespace Hierarchy

Attributes use dot notation to create namespaces:

text
service.name
service.version
service.instance.id

http.method
http.status_code
http.request_content_length

db.system
db.name
db.operation

Naming Rules

  1. Use lowercase with underscores: http.status_code not http.statusCode
  2. Use dots for namespaces: db.connection_string not db_connection_string
  3. Be specific but concise: http.request_content_length not http.request.content.length
  4. Use singular nouns: http.method not http.methods
  5. Avoid abbreviations: http.user_agent not http.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, not 1/0 or strings
  • URLs: Use full URLs: https://api.example.com/users not api.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 codes
  • db.system: Finite database types
  • http.route: Fixed route patterns

High cardinality ❌ (Avoid in attributes):

  • http.target: Every unique URL (/users/1, /users/2, ...)
  • user.id: One value per user
  • transaction.id: Unique per transaction
  • ip.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:

bash Go
go get go.opentelemetry.io/otel/semconv/v1.21.0
bash Python
pip install opentelemetry-semantic-conventions
bash Node.js
npm install @opentelemetry/semantic-conventions
bash Java
// Maven
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-semconv</artifactId>
    <version>1.21.0-alpha</version>
</dependency>

Version Compatibility

Semantic conventions evolve over time. When using them:

  1. Pin specific versions in production to avoid breaking changes
  2. Check compatibility between SDK and semantic conventions versions
  3. Update gradually when new conventions are released
  4. Monitor deprecations for attributes being phased out

Version format: v{major}.{minor}.{patch} (e.g., v1.21.0)

Best Practices

  1. Always use semantic conventions when available: Don't create custom attributes for common operations
  2. Set required attributes first: Ensure critical attributes are present before optional ones
  3. Keep values consistent: Use the same format across all services (e.g., always lowercase for enums)
  4. Document custom attributes: When semantic conventions don't cover your use case, document your custom attributes
  5. Validate in development: Check that attributes match conventions during testing
  6. Use constants, not strings: Import semantic convention packages to avoid typos
  7. Consider cardinality: Limit attribute values to reasonable sets
  8. 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:

text
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 ❌:

python
# DON'T: user_id creates millions of unique values
span.set_attribute("user.id", user_id)

Inconsistent formatting ❌:

python
# 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 ❌:

python
# DON'T: No namespace
span.set_attribute("cache_hit", True)

# DO: Use namespace
span.set_attribute("app.cache.hit", True)

Ignoring required attributes ❌:

python
# 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,
})

Uptrace and Semantic Conventions

Uptrace fully supports OpenTelemetry semantic conventions and applies normalization rules to make telemetry from heterogeneous sources easier to query.

To maintain compatibility with other tools like Prometheus and Tempo, Uptrace replaces dots with underscores in attribute names. For example, service.name becomes service_name.

Convention Evolution

The semantic conventions are actively maintained and versioned separately from the OpenTelemetry specification. They continue to expand to cover new technologies, protocols, and use cases while maintaining backward compatibility where possible.

To simplify migration between different versions, Uptrace allows you to select your desired semantic convention version on the Project Settings page. Once selected, Uptrace automatically renames certain attributes to follow the chosen convention. For example, it will rename deployment_environment to deployment_environment_name.

Uptrace does not modify existing rules to avoid breaking current queries, but we welcome suggestions for changes to include in the next supported version.

v1.33.0

Normalized attr nameCandidates
deployment_environment_namedeployment_environment, environment, env
service_nameservice, appname, application_name, fly_app_name
host_namehostname, host_hostname, host
db_namespacedb_name, db_cassandra_keyspace, db_hbase_namespace, dbname
db_system_namedb_system, db_type, db_dbms
db_collection_namedb_sql_table
db_operation_namedb_operation
db_query_textdb_statement
http_request_methodhttp_method, request_method, method
http_routehttp_server_route, route
log_severityseverity, error_severity, log_level, level
log_sourcesource_type, facility
log_file_pathlog_filepath, log_file
code_function_namecode_function
code_file_pathcode_filepath
code_line_numbercode_lineno
process_pidprocid, pid
server_addressnet_host_name, http_server_name, http_host
client_addresshttp_client_ip, net_peer_name, ip
messaging_message_idmessage_id, msgid
grouping_fingerprintlog_fingerprint, exception_fingerprint

v1.25.0

Normalized attr nameCandidates
deployment_environmentdeployment_environment_name, environment, env
service_nameservice, appname, application_name, fly_app_name
host_namehostname, host_hostname, host
enduser_iduser_id, user_identifier, user
db_namedb_cassandra_keyspace, db_hbase_namespace, dbname
db_systemdb_type, db_dbms
http_request_methodhttp_method, request_method, method
http_routehttp_server_route, route
http_response_status_codehttp_status_code
http_request_body_sizehttp_request_content_length
http_response_body_sizehttp_response_content_length
log_severityseverity, error_severity, log_level, level
log_sourcesource_type, facility
log_file_pathlog_filepath, log_file
cloud_regionfly_region
cloud_resource_idfaas_id
cloud_availability_zonecloud_zone
process_pidprocid, pid
url_schemehttp_scheme
url_fullhttp_url
url_pathhttp_target, request
faas_invocation_idfaas_execution
user_agent_originalhttp_user_agent, browser_user_agent, user_agent
network_protocol_namenet_app_protocol_name, messaging_protocol, http_flavor
network_protocol_versionnet_app_protocol_version, messaging_protocol_version
network_transportnet_transport
server_addressnet_host_name, http_server_name, http_host
server_portnet_host_port
server_socket_domainnet_sock_peer_name
server_socket_addressnet_host_ip, net_sock_host_addr
server_socket_portnet_host_port, net_sock_host_port
client_addresshttp_client_ip, net_peer_name, ip
client_socket_addressnet_peer_ip, net_sock_peer_addr
client_socket_portnet_peer_port, net_sock_peer_port
messaging_destination_namemessaging_destination
messaging_destination_kindmessaging_destination_kind
messaging_message_idmessage_id, msgid
messaging_message_typemessage_type
messaging_message_payload_size_bytesmessage_uncompressed_size
grouping_fingerprintlog_fingerprint, exception_fingerprint

Indexed Attributes

To store data, Uptrace uses ClickHouse, a columnar database that stores data in columns rather than rows. This means that instead of storing all data for a single record together, each column is stored separately.

To improve query performance, Uptrace places some attributes into separate columns. However, Uptrace cannot place all attributes into separate columns because it doesn't know the complete list of attributes in advance, and this list can become very large with thousands of attributes.

AttributeSpansLogsEventsPreaggregated
deployment_environmentYesYesYesYes
service_namespaceYesYesYesYes
service_nameYesYesYesYes
service_versionYesYesYesYes
host_nameYesYesYesYes
telemetry_sdk_nameYesYes
telemetry_sdk_languageYesYes
telemetry_sdk_versionYesYes
telemetry_auto_versionYesYes
otel_library_nameYesYesYes
otel_library_versionYesYesYes
client_addressYes
client_socket_addressYes
client_socket_portYes
db_systemYes
db_nameYes
db_sql_tableYes
db_statementYes
db_operationYes
process_pidYesYes
process_commandYesYes
process_runtime_nameYesYes
process_runtime_versionYesYes
process_runtime_descriptionYesYes
log_severityYes
log_file_pathYes
log_file_nameYes
log_iostreamYes
log_sourceYes
exception_typeYes
exception_messageYes
messaging_message_idYes
messaging_message_typeYes
messaging_message_payload_size_bytesYes

What's next?