Semantic Conventions

View Source

Standard attribute names for consistent observability data.

Overview

Semantic conventions define standard names for attributes, making telemetry data consistent and interoperable across services and tools.

Following these conventions ensures:

  • Data is queryable in observability backends
  • Dashboards and alerts work across services
  • Teams use consistent terminology

General Attributes

Service

AttributeTypeDescription
service.namestringLogical name of the service
service.versionstringVersion of the service
service.namespacestringNamespace for multi-tenant services
service.instance.idstringUnique instance identifier

Example:

instrument_tracer:set_attributes(#{
    <<"service.name">> => <<"order-service">>,
    <<"service.version">> => <<"1.2.3">>,
    <<"service.instance.id">> => node()
}).

Deployment

AttributeTypeDescription
deployment.environmentstringEnvironment name (production, staging)

HTTP Attributes

Client and Server

AttributeTypeDescription
http.methodstringHTTP method (GET, POST, etc.)
http.urlstringFull request URL
http.targetstringRequest target (path + query)
http.hoststringHost header value
http.schemestringURL scheme (http, https)
http.status_codeintHTTP response status code
http.flavorstringHTTP version (1.1, 2.0)
http.user_agentstringUser-Agent header
http.request_content_lengthintRequest body size in bytes
http.response_content_lengthintResponse body size in bytes

Server-specific

AttributeTypeDescription
http.routestringMatched route pattern
http.client_ipstringClient IP address

Example - HTTP Server:

instrument_tracer:with_span(<<"http_request">>, #{kind => server}, fun() ->
    instrument_tracer:set_attributes(#{
        <<"http.method">> => <<"POST">>,
        <<"http.target">> => <<"/api/orders">>,
        <<"http.route">> => <<"/api/orders">>,
        <<"http.scheme">> => <<"https">>,
        <<"http.host">> => <<"api.example.com">>,
        <<"http.status_code">> => 201,
        <<"http.client_ip">> => <<"192.168.1.100">>
    }),
    handle_request()
end).

Example - HTTP Client:

instrument_tracer:with_span(<<"http_call">>, #{kind => client}, fun() ->
    instrument_tracer:set_attributes(#{
        <<"http.method">> => <<"GET">>,
        <<"http.url">> => <<"https://api.example.com/users/123">>,
        <<"http.status_code">> => 200
    }),
    make_request()
end).

Client Span Conventions

Client spans represent outbound calls from your service. The instrument_client module simplifies creating properly attributed client spans.

Generic Client Attributes

AttributeTypeDescription
pool.namestringResource pool name
pool.typestringPool system type

Using instrument_client

The instrument_client module automatically sets semantic convention attributes based on the system type:

%% Database client - automatically sets db.* attributes
instrument_client:with_client_span(postgresql, <<"SELECT">>, #{
    target => <<"users">>,
    statement => <<"SELECT * FROM users">>,
    sanitize => true
}, fun() -> query(Conn, SQL) end).

%% HTTP client - automatically sets http.* attributes
instrument_client:with_client_span(http, <<"GET">>, #{
    target => <<"/api/users">>,
    attributes => #{<<"http.url">> => URL}
}, fun() -> httpc:request(URL) end).

%% Messaging - automatically sets messaging.* attributes
instrument_client:with_client_span(kafka, <<"publish">>, #{
    target => <<"orders-topic">>
}, fun() -> brod:produce(Client, Topic, Msg) end).

See the Client Tracing Guide for detailed examples.

Database Attributes

AttributeTypeDescription
db.systemstringDatabase type (postgresql, mysql, redis)
db.connection_stringstringConnection string (sanitized)
db.userstringDatabase user
db.namestringDatabase name
db.statementstringSQL statement or command
db.operationstringOperation type (SELECT, INSERT)
db.sql.tablestringTable being operated on
db.rows_returnedintNumber of rows returned
db.rows_affectedintNumber of rows affected

Example with instrument_client:

instrument_client:with_client_span(postgresql, <<"SELECT">>, #{
    target => <<"orders">>,
    statement => <<"SELECT * FROM orders WHERE id = $1">>,
    sanitize => true,
    attributes => #{
        <<"db.name">> => <<"orders">>,
        <<"db.user">> => <<"app_user">>
    }
}, fun() ->
    case execute_query() of
        {ok, Rows} ->
            instrument_client:set_response_attributes(#{
                rows_returned => length(Rows)
            }),
            {ok, Rows};
        Error ->
            Error
    end
end).

Manual Example:

instrument_tracer:with_span(<<"db_query">>, #{kind => client}, fun() ->
    instrument_tracer:set_attributes(#{
        <<"db.system">> => <<"postgresql">>,
        <<"db.name">> => <<"orders">>,
        <<"db.user">> => <<"app_user">>,
        <<"db.operation">> => <<"SELECT">>,
        <<"db.sql.table">> => <<"orders">>,
        <<"db.statement">> => <<"SELECT * FROM orders WHERE id = $1">>
    }),
    execute_query()
end).

Note: Avoid including sensitive data in db.statement. Use instrument_client:sanitize_text/1 or set sanitize => true.

Messaging Attributes

AttributeTypeDescription
messaging.systemstringMessaging system (rabbitmq, kafka)
messaging.destinationstringQueue/topic name
messaging.destination_kindstringKind (queue, topic)
messaging.message_idstringMessage identifier
messaging.conversation_idstringConversation/correlation ID
messaging.message_payload_size_bytesintMessage size
messaging.operationstringOperation (send, receive, process)

Example - Producer:

instrument_tracer:with_span(<<"send_message">>, #{kind => producer}, fun() ->
    instrument_tracer:set_attributes(#{
        <<"messaging.system">> => <<"rabbitmq">>,
        <<"messaging.destination">> => <<"orders.created">>,
        <<"messaging.destination_kind">> => <<"queue">>,
        <<"messaging.message_id">> => MessageId,
        <<"messaging.operation">> => <<"send">>
    }),
    publish_message()
end).

Example - Consumer:

instrument_tracer:with_span(<<"process_message">>, #{kind => consumer}, fun() ->
    instrument_tracer:set_attributes(#{
        <<"messaging.system">> => <<"rabbitmq">>,
        <<"messaging.destination">> => <<"orders.created">>,
        <<"messaging.message_id">> => MessageId,
        <<"messaging.operation">> => <<"process">>
    }),
    handle_message()
end).

RPC Attributes

AttributeTypeDescription
rpc.systemstringRPC system (grpc, thrift)
rpc.servicestringService name
rpc.methodstringMethod name
rpc.grpc.status_codeintgRPC status code

Example:

instrument_tracer:with_span(<<"grpc_call">>, #{kind => client}, fun() ->
    instrument_tracer:set_attributes(#{
        <<"rpc.system">> => <<"grpc">>,
        <<"rpc.service">> => <<"UserService">>,
        <<"rpc.method">> => <<"GetUser">>,
        <<"rpc.grpc.status_code">> => 0
    }),
    call_grpc()
end).

Exception Attributes

AttributeTypeDescription
exception.typestringException class/type
exception.messagestringException message
exception.stacktracestringStack trace as string

These are set automatically by record_exception/1,2.

Network Attributes

AttributeTypeDescription
net.transportstringTransport (ip_tcp, ip_udp)
net.peer.ipstringRemote IP address
net.peer.portintRemote port
net.peer.namestringRemote hostname
net.host.ipstringLocal IP address
net.host.portintLocal port
net.host.namestringLocal hostname

Erlang-specific Attributes

While not part of the OTel specification, these are useful for Erlang applications:

AttributeTypeDescription
erlang.process.pidstringProcess identifier
erlang.process.namestringRegistered process name
erlang.nodestringNode name
erlang.applicationstringOTP application
erlang.modulestringModule name
erlang.functionstringFunction name

Example:

instrument_tracer:set_attributes(#{
    <<"erlang.node">> => atom_to_binary(node()),
    <<"erlang.process.pid">> => list_to_binary(pid_to_list(self())),
    <<"erlang.module">> => <<"my_module">>,
    <<"erlang.function">> => <<"handle_call">>
}).

Custom Attributes

For domain-specific data, use a namespace prefix:

%% Order processing
instrument_tracer:set_attributes(#{
    <<"order.id">> => OrderId,
    <<"order.total">> => Total,
    <<"order.currency">> => <<"USD">>,
    <<"order.item_count">> => ItemCount,
    <<"order.customer_tier">> => <<"premium">>
}).

%% User context
instrument_tracer:set_attributes(#{
    <<"user.id">> => UserId,
    <<"user.email">> => Email,  %% Consider privacy implications
    <<"user.role">> => Role
}).

%% Feature flags
instrument_tracer:set_attributes(#{
    <<"feature.new_checkout">> => true,
    <<"feature.ab_test_variant">> => <<"B">>
}).

Metric Naming Conventions

Counter Names

  • Use _total suffix
  • Describe what is being counted
  • Use lowercase with underscores
http_requests_total
db_queries_total
messages_sent_total
errors_total

Gauge Names

  • Describe current state
  • No special suffix needed
http_active_connections
db_pool_size
queue_length
cache_size_bytes

Histogram Names

  • Include unit in name: _seconds, _bytes
  • Describe what is measured
http_request_duration_seconds
db_query_duration_seconds
response_size_bytes
message_size_bytes

Label Names

  • Use lowercase with underscores
  • Keep names short but descriptive
  • Be consistent across metrics
method      %% HTTP method
status      %% Status code or status name
endpoint    %% API endpoint
operation   %% Database operation
pool        %% Connection pool name

Best Practices

Do

  • Use standard attribute names when they exist
  • Namespace custom attributes
  • Include relevant context for debugging
  • Be consistent across your codebase

Don't

  • Include sensitive data (passwords, tokens)
  • Use high-cardinality attribute values excessively
  • Duplicate information in span name and attributes
  • Create new names when standards exist

Attribute Value Guidelines

  • Strings: Use lowercase, consistent formats
  • Numbers: Use standard units (seconds, bytes)
  • Booleans: Use for feature flags, binary states
  • Arrays: Avoid unless necessary, impacts cardinality

Privacy Considerations

Be careful with:

  • User emails
  • IP addresses
  • Request bodies
  • Authentication tokens
  • Personal identifiers

Consider:

  • Hashing sensitive values
  • Using IDs instead of raw values
  • Applying attribute filtering before export