前言
OpenTelemetry作为目前可观察性的标准方案,近年来的发展速度非常快,目前OpenTelemetry Trace规范v1.0版本已经发布,Metrics v1.0也将在几个月后发布。相对最慢的当属Log规范,但在众多公司的参与下,Log规范的第一个版本已经在半年前发布,且经过了几次更新,也在朝着v1.0的方向快速前进。
本文主要介绍OpenTelemetry Log规范,这一规范来自于Google、Microsoft、AWS、Splunk、DataDog、ES、Fluntd等众多优秀的公司和项目成员,其中有很多点是我们在平时开发、运维需要关注的知识和经验,值得大家一观。
初衷&宗旨
官方定义的宗旨如下:
- 日志模型能够表达各种来源的日志:应用程序、机器事件、系统日志、容器日志等。
- 能够将现有的大部分日志格式映射到日志模型,并且日志模型也可以很容易的转换成各种日志格式。
- 通过日志数据模型和语义的定义,能够指导日志系统如何记录、传输、存储和理解日志。
从OpenTelemetry的顶层宗旨来看,日志模型的定义最主要还是规范化,能够将Metrics、Tracing、Logging的common schema统一,这样可以做到3者的无缝打通。当然,为了尽可能的通用,模型的定义会参考众多的日志格式,表达信息的方式需要尽肯能灵活。
日志架构演进
OpenTelemetryCollection
SeparateCollection
lnfraHostivM/Pod/Container
Logs
Traces
Application
Metrics
lnfraHostM/Pod/Container
AutoandManualinstrumentation
RawData
Application
EnrchedData
Metric
Logging
Tracing
lntra
App
systemAPP
lnfra
Attributes
libraries
libraries
Traces
libraries
Logs
metncs
metncs
Logs
App
App
Host
Traces
Logs
Metncs
Traces
Logs
metncs
metncs
System
Logs
EnrichmentProcessor
MetncAgent
OpenTelemetry
FiIle
TracingAgent
Collector
e.g.
Logs
(e.g.Jaeger
OpenCensus
Agent)
Exporter
Agent)
LogCollection
Comelatod
Agent(e.g.
Telemetry
FluentBit)
Tracing
Traces
Logs
Metrics
MetricBackend
LoggingBackond
correlate
correlato
Backend
Backond(s)
传统架构下Logs、Traces、Metrics都是单独产生并收集的,在OpenTelemetry的规划中,所有的数据都会通过OpenTelemetry的Collector进行采集并传输到统一的后端进行关联。好处非常明显:
- 应用只需要一个SDK就可以做所有可观测性上的事情,依赖更少、资源消耗也更小
- 采集端只需要一个,部署和运维代价更低
- 数据格式统一,数据关联更容易
上面的图展示的是终极目标,但目前OpenTelemetry Collector对于Log的支持还比较弱,估计未来1-2年还是要用其他的日志采集去支撑。
特性
- LogModel(日志模型)应该支持任意类型的日志能够明确的转换成该类型,对于两种相同含义不同格式的日志,转换到LogModel后应该是完全等价的。
- 从其他日志类型映射到LogModel是有一定意义的,LogModel必须能够表示出其他日志类型的语义。
- 将类型A的日志换成到LogModel,然后再由LogModel转换到类型B。这种场景应该和从类型A直接转换到类型B是一致的,即转换过程中不应该丢失/增加数据。
- LogModel在传输和存储的效率上需尽可能的高,主要是对于CPU和内存的使用尽可能低,即序列化/反序列化尽可能高效以及存储空间尽可能低。
从表达能力上看,LogModel需要能够表达至少以下3种的日志/Event:
- 系统日志:即操作系统/硬件产生的日志,一个最典型的代表是Syslog。
- 三方应用日志:一些流行的三方软件的日志格式,例如Apache日志、MySQL慢日志等。
- 应用日志:业务应用产生的日志,这些日志一般由程序员打印,在需要的时候可以适当修改源码来适应新的LogModel。
字段类型
LogModel只定义日志(Record)的逻辑表现形式,与具体的物理格式和编码形式无关,每个Record包含两种字段类型:
- 具有特定类型和含义的顶级字段(Top-Level)
- 具体的字段,通常以KeyValue对的形式,根据不同的Top-Level名称,对应的字段会有不同的类型
LogModel定义
字段名 | 描述 | 必选 |
Timestamp | 日志时间戳 | 是 |
TraceId | 关联请求的TraceId | 否 |
SpanId | 关联请求的SpanId | 否 |
TraceFlags | W3C trace flag. | 否 |
SeverityText | 日志等级的可读描述. | 否 |
SeverityNumber | 日志等级. | 否 |
ShortName | 用于标识日志类型的短语. | 否 |
Body | 具体的日志内容. | 否 |
Resource | Log关联的Resource. | 否 |
Attributes | 额外关联属性. | 否 |
字段详细解释
Timestamp
uint64,纳秒
TraceId
字节数组,具体可以参考:W3C Trace Context
SpanId
字节数组,如果SpanId出现,则TraceId必须出现。
TraceFlags
单字节,具体可参考:W3C Trace Context
SeverityText
日志等级的可读描述,如果不设置,则按照SeverityNumber
的默认Mapping规则映射。
SeverityNumber
SeverityNumber
和Syslog中的日志等级比较像,OpenTelemetry定义了6大类、24种日志等级,基本上可以包含所有类型日志的等级定义。
SeverityNumber range | 通用等级 | 含义 |
1-4 | TRACE | A fine-grained debugging event. Typically disabled in default configurations. |
5-8 | DEBUG | A debugging event. |
9-12 | INFO | An informational event. Indicates that an event happened. |
13-16 | WARN | A warning event. Not an error but is likely more important than an informational event. |
17-20 | ERROR | An error event. Something went wrong. |
21-24 | FATAL | A fatal error such as application or system crash. |
SeverityNumber
和SeverityText
可通过Mapping的方式自动映射,所以在日志产生的时候,可以不填写SeverityText
字段,以介绍序列化/反序列化和传输代价。映射关系如下:
SeverityNumber | Short Name |
1 | TRACE |
2 | TRACE2 |
3 | TRACE3 |
4 | TRACE4 |
5 | DEBUG |
6 | DEBUG2 |
7 | DEBUG3 |
8 | DEBUG4 |
9 | INFO |
10 | INFO2 |
11 | INFO3 |
12 | INFO4 |
13 | WARN |
14 | WARN2 |
15 | WARN3 |
16 | WARN4 |
17 | ERROR |
18 | ERROR2 |
19 | ERROR3 |
20 | ERROR4 |
21 | FATAL |
22 | FATAL2 |
23 | FATAL3 |
24 | FATAL4 |
ShortName
一般用一个特定的词来标识日志类型,通常不要超过50个字节,例如:ProcessStarted
Body
日志内容为Any类型,Any类型,可以是 int、string、bool、float,也可以是一个数组或是Map。
Resource
Key/Value对列表,参考OpenTelemetry的通用Resource定义。
这里包括主机名、进程号、服务名等信息,可以用于关联Metrics、Tracing。
Attributes
Key/Value对列表,Key始终是 string,Value是Any类型。具体可以参考Tracing中Attributes的定义。
LogModel示例
Example 1
{ "Timestamp": 1586960586000, // JSON needs to make a decision about// how to represent nanoseconds."Attributes": { "http.status_code": 500, "http.url": "http://example.com", "my.custom.application.tag": "hello", }, "Resource": { "service.name": "donut_shop", "service.version": "semver:2.0.0", "k8s.pod.uid": "1138528c-c36e-11e9-a1a7-42010a800198", }, "TraceId": "f4dbb3edd765f620", // this is a byte sequence// (hex-encoded in JSON)"SpanId": "43222c2d51a7abe3", "SeverityText": "INFO", "SeverityNumber": 9, "Body": "20200415T072306-0700 INFO I like donuts"}
Example 2
{ "Timestamp": 1586960586000, "Body": { "i": "am", "an": "event", "of": { "some": "complexity" } } }
Example 3
{ "Timestamp": 1586960586000, "Attributes":{ "http.scheme":"https", "http.host":"donut.mycie.com", "http.target":"/order", "http.method":"post", "http.status_code":500, "http.flavor":"1.1", "http.user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36", } }
总结
从上述的规范可以看出,OpenTelemetry的Log模型主要追求以下几点:
- 作为Metrics、Traces、Logs三大可观测性数据的终极细节性数据,Log需要能够表达足够详细的信息
- 如果Log在Trace场景中,需要包裹TraceID、SpanID,以便和Traces关联,同时借助Resource信息可以更好的和Traces、Metrics关联
- Log的性能需要尽可能的高,因为日志是数据量最大的可观测性数据,而且不会做采样,一定要确保对应用的性能影响尽可能的小
- 兼容性要好,因为目前有太多的日志系统,且都存在了很久也有很明确的存在意义,所以需要能够保证这些不同类型的Log可以和OpenTelemetry Log进行无缝转换
整体上这个模型还是非常适合现代的IT系统,但想要把这套标准成功实施下来还有很多工作要做,包括日志采集、解析、传输工具,兼容其他很多已有系统、环境等。不过好在Fluentd也在CNCF项目下,未来可能会成为OpenTelemetry的日志采集内核,配合Collector一起工作。
参考
- https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/overview.md
- https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md
- https://www.w3.org/TR/trace-context/#trace-id
- https://docs.datadoghq.com/tracing/connect_logs_and_traces/java/?tab=log4j2
- https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/overview.md#resources
- https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/trace/semantic_conventions