# OpenTelemetry Semantic Conventions - Standard Attributes for Traces, Metrics & Logs

> OpenTelemetry semantic conventions provide standardized attribute names and values for common operations like HTTP requests, database queries, and RPC calls. Learn naming rules, resource attributes, and how to implement them across languages.

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:

<table>
<thead>
  <tr>
    <th>
      Attribute
    </th>
    
    <th>
      Description
    </th>
    
    <th>
      Example
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        service.name
      </code>
    </td>
    
    <td>
      Logical service name
    </td>
    
    <td>
      <code>
        checkout-api
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        service.version
      </code>
    </td>
    
    <td>
      Service version
    </td>
    
    <td>
      <code>
        1.2.3
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        service.namespace
      </code>
    </td>
    
    <td>
      Service namespace
    </td>
    
    <td>
      <code>
        production
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        service.instance.id
      </code>
    </td>
    
    <td>
      Unique instance identifier
    </td>
    
    <td>
      <code>
        checkout-api-7f8b9c-abc123
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        deployment.environment
      </code>
    </td>
    
    <td>
      Deployment environment
    </td>
    
    <td>
      <code>
        production
      </code>
      
      , <code>
        staging
      </code>
      
      , <code>
        dev
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        host.name
      </code>
    </td>
    
    <td>
      Hostname
    </td>
    
    <td>
      <code>
        web-server-01
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        container.name
      </code>
    </td>
    
    <td>
      Container name
    </td>
    
    <td>
      <code>
        checkout-api-container
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        k8s.pod.name
      </code>
    </td>
    
    <td>
      Kubernetes pod name
    </td>
    
    <td>
      <code>
        checkout-api-7f8b9c-abc123
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        cloud.provider
      </code>
    </td>
    
    <td>
      Cloud provider
    </td>
    
    <td>
      <code>
        aws
      </code>
      
      , <code>
        gcp
      </code>
      
      , <code>
        azure
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        cloud.region
      </code>
    </td>
    
    <td>
      Cloud region
    </td>
    
    <td>
      <code>
        us-east-1
      </code>
    </td>
  </tr>
</tbody>
</table>

<code-group>

```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"
    )));
```

</code-group>

### Span Attributes

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

<code-group>

```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"
));
```

</code-group>

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

<table>
<thead>
  <tr>
    <th>
      Attribute
    </th>
    
    <th>
      Required
    </th>
    
    <th>
      Description
    </th>
    
    <th>
      Example
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        http.method
      </code>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      HTTP method
    </td>
    
    <td>
      <code>
        GET
      </code>
      
      , <code>
        POST
      </code>
      
      , <code>
        PUT
      </code>
      
      , <code>
        DELETE
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        http.url
      </code>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      Full URL
    </td>
    
    <td>
      <code>
        https://api.example.com/users/123
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        http.status_code
      </code>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      HTTP response status
    </td>
    
    <td>
      <code>
        200
      </code>
      
      , <code>
        404
      </code>
      
      , <code>
        500
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        http.scheme
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Protocol scheme
    </td>
    
    <td>
      <code>
        http
      </code>
      
      , <code>
        https
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        http.target
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Request target
    </td>
    
    <td>
      <code>
        /users/123?page=1
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        http.host
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Host header value
    </td>
    
    <td>
      <code>
        api.example.com
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        http.user_agent
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      User agent string
    </td>
    
    <td>
      <code>
        Mozilla/5.0...
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        http.request_content_length
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Request body size
    </td>
    
    <td>
      <code>
        1024
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        http.response_content_length
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Response body size
    </td>
    
    <td>
      <code>
        2048
      </code>
    </td>
  </tr>
</tbody>
</table>

<code-group>

```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();
}
```

</code-group>

### HTTP Server Spans

When your service receives an HTTP request:

<table>
<thead>
  <tr>
    <th>
      Attribute
    </th>
    
    <th>
      Required
    </th>
    
    <th>
      Description
    </th>
    
    <th>
      Example
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        http.method
      </code>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      HTTP method
    </td>
    
    <td>
      <code>
        GET
      </code>
      
      , <code>
        POST
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        http.route
      </code>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      Matched route pattern
    </td>
    
    <td>
      <code>
        /api/users/:id
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        http.status_code
      </code>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      Response status
    </td>
    
    <td>
      <code>
        200
      </code>
      
      , <code>
        404
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        http.target
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Request target
    </td>
    
    <td>
      <code>
        /api/users/123
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        http.client_ip
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Client IP address
    </td>
    
    <td>
      <code>
        192.168.1.100
      </code>
    </td>
  </tr>
</tbody>
</table>

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:

<table>
<thead>
  <tr>
    <th>
      Attribute
    </th>
    
    <th>
      Required
    </th>
    
    <th>
      Description
    </th>
    
    <th>
      Example
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        db.system
      </code>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      Database system
    </td>
    
    <td>
      <code>
        postgresql
      </code>
      
      , <code>
        mysql
      </code>
      
      , <code>
        mongodb
      </code>
      
      , <code>
        redis
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        db.name
      </code>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      Database name
    </td>
    
    <td>
      <code>
        users_db
      </code>
      
      , <code>
        inventory
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        db.operation
      </code>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      Operation type
    </td>
    
    <td>
      <code>
        SELECT
      </code>
      
      , <code>
        INSERT
      </code>
      
      , <code>
        UPDATE
      </code>
      
      , <code>
        DELETE
      </code>
      
      , <code>
        findOne
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        db.statement
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Database statement
    </td>
    
    <td>
      <code>
        SELECT * FROM users WHERE id = ?
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        db.connection_string
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Connection string (no credentials)
    </td>
    
    <td>
      <code>
        postgresql://db.example.com:5432/users_db
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        db.user
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Database username
    </td>
    
    <td>
      <code>
        app_user
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        db.sql.table
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Primary table name
    </td>
    
    <td>
      <code>
        users
      </code>
    </td>
  </tr>
</tbody>
</table>

<code-group>

```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();
}
```

</code-group>

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

<table>
<thead>
  <tr>
    <th>
      Attribute
    </th>
    
    <th>
      Required
    </th>
    
    <th>
      Description
    </th>
    
    <th>
      Example
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        rpc.system
      </code>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      RPC system
    </td>
    
    <td>
      <code>
        grpc
      </code>
      
      , <code>
        java_rmi
      </code>
      
      , <code>
        wcf
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        rpc.service
      </code>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      Service name
    </td>
    
    <td>
      <code>
        myservice.EchoService
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        rpc.method
      </code>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      Method name
    </td>
    
    <td>
      <code>
        Echo
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        rpc.grpc.status_code
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      gRPC status code
    </td>
    
    <td>
      <code>
        0
      </code>
      
       (OK), <code>
        2
      </code>
      
       (UNKNOWN)
    </td>
  </tr>
</tbody>
</table>

<code-group>

```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
```

</code-group>

## Messaging Semantic Conventions

For message queues and pub/sub systems:

<table>
<thead>
  <tr>
    <th>
      Attribute
    </th>
    
    <th>
      Required
    </th>
    
    <th>
      Description
    </th>
    
    <th>
      Example
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        messaging.system
      </code>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      Messaging system
    </td>
    
    <td>
      <code>
        kafka
      </code>
      
      , <code>
        rabbitmq
      </code>
      
      , <code>
        aws_sqs
      </code>
      
      , <code>
        google_pubsub
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        messaging.destination
      </code>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      Destination name
    </td>
    
    <td>
      <code>
        orders-topic
      </code>
      
      , <code>
        notifications-queue
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        messaging.operation
      </code>
    </td>
    
    <td>
      ✅ Yes
    </td>
    
    <td>
      Operation type
    </td>
    
    <td>
      <code>
        publish
      </code>
      
      , <code>
        receive
      </code>
      
      , <code>
        process
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        messaging.message_id
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Message identifier
    </td>
    
    <td>
      <code>
        msg-123456
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        messaging.conversation_id
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Conversation/correlation ID
    </td>
    
    <td>
      <code>
        order-789
      </code>
    </td>
  </tr>
</tbody>
</table>

<code-group>

```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)
```

</code-group>

## 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`<br />

**Python**: `opentelemetry-semantic-conventions`<br />

**JavaScript/Node.js**: `@opentelemetry/semantic-conventions`<br />

**Java**: `io.opentelemetry:opentelemetry-semconv`<br />

**Ruby**: `opentelemetry-semantic_conventions`<br />

**.NET**: `OpenTelemetry.SemanticConventions`<br />

**PHP**: `open-telemetry/sem-conv`

Install the package and import semantic attributes:

<code-group>

```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>
```

</code-group>

## 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](https://github.com/open-telemetry/semantic-conventions/releases) 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

<table>
<thead>
  <tr>
    <th>
      Normalized attr name
    </th>
    
    <th>
      Candidates
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      deployment_environment_name
    </td>
    
    <td>
      deployment_environment, environment, env
    </td>
  </tr>
  
  <tr>
    <td>
      service_name
    </td>
    
    <td>
      service, appname, application_name, fly_app_name
    </td>
  </tr>
  
  <tr>
    <td>
      host_name
    </td>
    
    <td>
      hostname, host_hostname, host
    </td>
  </tr>
  
  <tr>
    <td>
      db_namespace
    </td>
    
    <td>
      db_name, db_cassandra_keyspace, db_hbase_namespace, dbname
    </td>
  </tr>
  
  <tr>
    <td>
      db_system_name
    </td>
    
    <td>
      db_system, db_type, db_dbms
    </td>
  </tr>
  
  <tr>
    <td>
      db_collection_name
    </td>
    
    <td>
      db_sql_table
    </td>
  </tr>
  
  <tr>
    <td>
      db_operation_name
    </td>
    
    <td>
      db_operation
    </td>
  </tr>
  
  <tr>
    <td>
      db_query_text
    </td>
    
    <td>
      db_statement
    </td>
  </tr>
  
  <tr>
    <td>
      http_request_method
    </td>
    
    <td>
      http_method, request_method, method
    </td>
  </tr>
  
  <tr>
    <td>
      http_route
    </td>
    
    <td>
      http_server_route, route
    </td>
  </tr>
  
  <tr>
    <td>
      log_severity
    </td>
    
    <td>
      severity, error_severity, log_level, level
    </td>
  </tr>
  
  <tr>
    <td>
      log_source
    </td>
    
    <td>
      source_type, facility
    </td>
  </tr>
  
  <tr>
    <td>
      log_file_path
    </td>
    
    <td>
      log_filepath, log_file
    </td>
  </tr>
  
  <tr>
    <td>
      code_function_name
    </td>
    
    <td>
      code_function
    </td>
  </tr>
  
  <tr>
    <td>
      code_file_path
    </td>
    
    <td>
      code_filepath
    </td>
  </tr>
  
  <tr>
    <td>
      code_line_number
    </td>
    
    <td>
      code_lineno
    </td>
  </tr>
  
  <tr>
    <td>
      process_pid
    </td>
    
    <td>
      procid, pid
    </td>
  </tr>
  
  <tr>
    <td>
      server_address
    </td>
    
    <td>
      net_host_name, http_server_name, http_host
    </td>
  </tr>
  
  <tr>
    <td>
      client_address
    </td>
    
    <td>
      http_client_ip, net_peer_name, ip
    </td>
  </tr>
  
  <tr>
    <td>
      messaging_message_id
    </td>
    
    <td>
      message_id, msgid
    </td>
  </tr>
  
  <tr>
    <td>
      grouping_fingerprint
    </td>
    
    <td>
      log_fingerprint, exception_fingerprint
    </td>
  </tr>
</tbody>
</table>

#### v1.25.0

<table>
<thead>
  <tr>
    <th>
      Normalized attr name
    </th>
    
    <th>
      Candidates
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      deployment_environment
    </td>
    
    <td>
      deployment_environment_name, environment, env
    </td>
  </tr>
  
  <tr>
    <td>
      service_name
    </td>
    
    <td>
      service, appname, application_name, fly_app_name
    </td>
  </tr>
  
  <tr>
    <td>
      host_name
    </td>
    
    <td>
      hostname, host_hostname, host
    </td>
  </tr>
  
  <tr>
    <td>
      enduser_id
    </td>
    
    <td>
      user_id, user_identifier, user
    </td>
  </tr>
  
  <tr>
    <td>
      db_name
    </td>
    
    <td>
      db_cassandra_keyspace, db_hbase_namespace, dbname
    </td>
  </tr>
  
  <tr>
    <td>
      db_system
    </td>
    
    <td>
      db_type, db_dbms
    </td>
  </tr>
  
  <tr>
    <td>
      http_request_method
    </td>
    
    <td>
      http_method, request_method, method
    </td>
  </tr>
  
  <tr>
    <td>
      http_route
    </td>
    
    <td>
      http_server_route, route
    </td>
  </tr>
  
  <tr>
    <td>
      http_response_status_code
    </td>
    
    <td>
      http_status_code
    </td>
  </tr>
  
  <tr>
    <td>
      http_request_body_size
    </td>
    
    <td>
      http_request_content_length
    </td>
  </tr>
  
  <tr>
    <td>
      http_response_body_size
    </td>
    
    <td>
      http_response_content_length
    </td>
  </tr>
  
  <tr>
    <td>
      log_severity
    </td>
    
    <td>
      severity, error_severity, log_level, level
    </td>
  </tr>
  
  <tr>
    <td>
      log_source
    </td>
    
    <td>
      source_type, facility
    </td>
  </tr>
  
  <tr>
    <td>
      log_file_path
    </td>
    
    <td>
      log_filepath, log_file
    </td>
  </tr>
  
  <tr>
    <td>
      cloud_region
    </td>
    
    <td>
      fly_region
    </td>
  </tr>
  
  <tr>
    <td>
      cloud_resource_id
    </td>
    
    <td>
      faas_id
    </td>
  </tr>
  
  <tr>
    <td>
      cloud_availability_zone
    </td>
    
    <td>
      cloud_zone
    </td>
  </tr>
  
  <tr>
    <td>
      process_pid
    </td>
    
    <td>
      procid, pid
    </td>
  </tr>
  
  <tr>
    <td>
      url_scheme
    </td>
    
    <td>
      http_scheme
    </td>
  </tr>
  
  <tr>
    <td>
      url_full
    </td>
    
    <td>
      http_url
    </td>
  </tr>
  
  <tr>
    <td>
      url_path
    </td>
    
    <td>
      http_target, request
    </td>
  </tr>
  
  <tr>
    <td>
      faas_invocation_id
    </td>
    
    <td>
      faas_execution
    </td>
  </tr>
  
  <tr>
    <td>
      user_agent_original
    </td>
    
    <td>
      http_user_agent, browser_user_agent, user_agent
    </td>
  </tr>
  
  <tr>
    <td>
      network_protocol_name
    </td>
    
    <td>
      net_app_protocol_name, messaging_protocol, http_flavor
    </td>
  </tr>
  
  <tr>
    <td>
      network_protocol_version
    </td>
    
    <td>
      net_app_protocol_version, messaging_protocol_version
    </td>
  </tr>
  
  <tr>
    <td>
      network_transport
    </td>
    
    <td>
      net_transport
    </td>
  </tr>
  
  <tr>
    <td>
      server_address
    </td>
    
    <td>
      net_host_name, http_server_name, http_host
    </td>
  </tr>
  
  <tr>
    <td>
      server_port
    </td>
    
    <td>
      net_host_port
    </td>
  </tr>
  
  <tr>
    <td>
      server_socket_domain
    </td>
    
    <td>
      net_sock_peer_name
    </td>
  </tr>
  
  <tr>
    <td>
      server_socket_address
    </td>
    
    <td>
      net_host_ip, net_sock_host_addr
    </td>
  </tr>
  
  <tr>
    <td>
      server_socket_port
    </td>
    
    <td>
      net_host_port, net_sock_host_port
    </td>
  </tr>
  
  <tr>
    <td>
      client_address
    </td>
    
    <td>
      http_client_ip, net_peer_name, ip
    </td>
  </tr>
  
  <tr>
    <td>
      client_socket_address
    </td>
    
    <td>
      net_peer_ip, net_sock_peer_addr
    </td>
  </tr>
  
  <tr>
    <td>
      client_socket_port
    </td>
    
    <td>
      net_peer_port, net_sock_peer_port
    </td>
  </tr>
  
  <tr>
    <td>
      messaging_destination_name
    </td>
    
    <td>
      messaging_destination
    </td>
  </tr>
  
  <tr>
    <td>
      messaging_destination_kind
    </td>
    
    <td>
      messaging_destination_kind
    </td>
  </tr>
  
  <tr>
    <td>
      messaging_message_id
    </td>
    
    <td>
      message_id, msgid
    </td>
  </tr>
  
  <tr>
    <td>
      messaging_message_type
    </td>
    
    <td>
      message_type
    </td>
  </tr>
  
  <tr>
    <td>
      messaging_message_payload_size_bytes
    </td>
    
    <td>
      message_uncompressed_size
    </td>
  </tr>
  
  <tr>
    <td>
      grouping_fingerprint
    </td>
    
    <td>
      log_fingerprint, exception_fingerprint
    </td>
  </tr>
</tbody>
</table>

### 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.

<table>
<thead>
  <tr>
    <th>
      Attribute
    </th>
    
    <th>
      Spans
    </th>
    
    <th>
      Logs
    </th>
    
    <th>
      Events
    </th>
    
    <th>
      Preaggregated
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      deployment_environment
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
  </tr>
  
  <tr>
    <td>
      service_namespace
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
  </tr>
  
  <tr>
    <td>
      service_name
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
  </tr>
  
  <tr>
    <td>
      service_version
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
  </tr>
  
  <tr>
    <td>
      host_name
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
  </tr>
  
  <tr>
    <td>
      telemetry_sdk_name
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      telemetry_sdk_language
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      telemetry_sdk_version
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      telemetry_auto_version
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      otel_library_name
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      otel_library_version
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      client_address
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      client_socket_address
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      client_socket_port
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      db_system
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      db_name
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      db_sql_table
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      db_statement
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      db_operation
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      process_pid
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      process_command
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      process_runtime_name
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      process_runtime_version
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      process_runtime_description
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      log_severity
    </td>
    
    <td>
      
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      log_file_path
    </td>
    
    <td>
      
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      log_file_name
    </td>
    
    <td>
      
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      log_iostream
    </td>
    
    <td>
      
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      log_source
    </td>
    
    <td>
      
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      exception_type
    </td>
    
    <td>
      
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      exception_message
    </td>
    
    <td>
      
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      messaging_message_id
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      messaging_message_type
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      messaging_message_payload_size_bytes
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      
    </td>
  </tr>
</tbody>
</table>

## What's next?

- [Learn about OpenTelemetry Distributed Tracing](/opentelemetry/distributed-tracing)
- [Learn about OpenTelemetry Metrics](/opentelemetry/metrics)
- [Learn about OpenTelemetry Logs](/opentelemetry/logs)
- [Get started with OpenTelemetry](/get)
