OTel
OpenTelemetry defines a log record together with a set of semantic conventions: standard attribute names such as host.name, http.request.method and service.name.
DataStream converts to and from OTel with the Normalize processor, using otel as the source_format or target_format. This is separate from the OTLP transport: normalizing to otel changes a record's field names, and does not send it anywhere.
Record Shape
A record normalized to otel is flat. Its fields are keyed by semantic-convention attribute names, and it carries four log-record fields beside them:
| Field | Source | Description |
|---|---|---|
body | message | The log message |
severity_text | log.level | The severity as written by the source |
severity_number | log.syslog.severity.code | The OTel severity number, 1-24. See below |
time_unix_nano | @timestamp | The event time, in Unix nanoseconds |
The record is not wrapped in an OTLP resourceLogs envelope and is not nested; every key sits at the top level, dots included.
Attribute Mapping
ECS fields with a semantic-convention counterpart are renamed to it:
| ECS Field | OTel Field |
|---|---|
host.name, host.hostname | host.name |
host.id, cloud.instance.id | host.id |
host.architecture | host.arch |
host.type, cloud.machine.type | host.type |
host.ip | host.ip |
host.mac | host.mac |
host.os.name | os.name |
host.os.version | os.version |
host.os.type | os.type |
host.os.full | os.description |
cloud.provider | cloud.provider |
cloud.region | cloud.region |
cloud.availability_zone | cloud.availability_zone |
cloud.account.id | cloud.account.id |
cloud.service.name | cloud.platform |
service.name | service.name |
service.version | service.version |
service.environment | deployment.environment.name |
service.node.name | service.instance.id |
container.id | container.id |
container.name | container.name |
container.image.name | container.image.name |
container.runtime | container.runtime |
source.ip, source.address | source.address |
source.port | source.port |
destination.ip, destination.address | destination.address |
destination.port | destination.port |
client.ip, client.address | client.address |
client.port | client.port |
server.ip, server.address | server.address |
server.port | server.port |
network.transport | network.transport |
network.type | network.type |
network.protocol | network.protocol.name |
http.version | network.protocol.version |
url.full, url.original | url.full |
url.path | url.path |
url.query | url.query |
url.scheme | url.scheme |
url.domain | url.domain |
url.port | url.port |
url.fragment | url.fragment |
http.request.method | http.request.method |
http.request.bytes | http.request.size |
http.request.body.bytes | http.request.body.size |
http.response.status_code | http.response.status_code |
http.response.bytes | http.response.size |
http.response.body.bytes | http.response.body.size |
user_agent.original | user_agent.original |
user_agent.name | user_agent.name |
user_agent.version | user_agent.version |
user.name | user.name |
user.id | user.id |
user.email | user.email |
user.full_name | user.full_name |
user.hash | user.hash |
user.roles | user.roles |
process.pid | process.pid |
process.parent.pid | process.parent_pid |
process.name | process.executable.name |
process.executable | process.executable.path |
process.command_line | process.command_line |
process.args | process.command_args |
error.type | error.type |
error.message | exception.message |
error.stack_trace | exception.stacktrace |
log.file.path | log.file.path |
event.original | log.record.original |
Where two ECS fields share one OTel field, the one that sorts later alphabetically wins when both carry a value. For example, host.name wins over host.hostname, and url.original over url.full. An empty value never displaces a populated one.
The same attribute table is used by the OTLP target when it maps a record at egress, so a record normalized to otel and a record the target maps on the way out use the same semantic-convention names.
Unmapped Fields
Fields with no entry in the table pass through unrenamed. OTel's key convention is ECS's own, lowercase and dot-separated, so there is no name-conversion fallback of the kind ASIM or CSL apply: event.action stays event.action.
Severity Levels
severity_number uses the OTel ranges: TRACE 1-4, DEBUG 5-8, INFO 9-12, WARN 13-16, ERROR 17-20, FATAL 21-24.
It is derived from severity_text first. The value is matched case-insensitively against the words below:
| Number | Recognized Values |
|---|---|
1 | trace |
5 | debug, verbose |
9 | info, information, informational |
10 | notice |
13 | warn, warning |
17 | err, error |
19 | crit, critical |
21 | alert, fatal |
23 | emerg, emergency, panic |
When severity_text is absent or not a recognized word, the syslog severity code carried over from log.syslog.severity.code is used instead:
| Syslog Code | Name | Number |
|---|---|---|
0 | Emergency | 23 |
1 | Alert | 21 |
2 | Critical | 19 |
3 | Error | 17 |
4 | Warning | 13 |
5 | Notice | 10 |
6 | Informational | 9 |
7 | Debug | 5 |
Timestamps
time_unix_nano is converted from the @timestamp value it was renamed from. An RFC 3339 string, a zone-less date-time, or a numeric epoch are all accepted; the unit of an epoch is inferred from its magnitude.
A timestamp that cannot be parsed keeps its original value rather than becoming 0, so a bad input stays visible instead of silently zeroing.
The severity and timestamp conversions are the schema enforcement pass, which is skipped when the processor is run with ignore_rules set. Without it, the four log-record fields are renamed but keep their original values.
Converting from OTel
With source_format: otel, the mapping runs in reverse. severity_number is converted back to a syslog code at log.syslog.severity.code, and time_unix_nano back to an RFC 3339 @timestamp. These conversions are also skipped when ignore_rules is set.
Format Detection
OTel has no characteristic field, so it is never auto-detected. To normalize a record that is already OTel-shaped, set source_format: otel explicitly. It is inferred only when an earlier normalize step in the same pipeline already produced an otel record.
Example
An ECS web access event converted to OTel:
{
"time_unix_nano": 1726671509000000000,
"severity_text": "info",
"severity_number": 9,
"body": "GET /index.html 200",
"host.name": "web-01",
"service.name": "nginx",
"source.address": "10.1.100.11",
"source.port": 54190,
"http.request.method": "GET",
"http.response.status_code": 200,
"url.path": "/index.html",
"event.action": "access"
}