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

What's next?