1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2024-12-28 21:09:17 +02:00
opentelemetry-go/sdk/log/DESIGN.md
2024-09-09 08:53:15 +02:00

8.2 KiB

Logs SDK

Abstract

go.opentelemetry.io/otel/sdk/log provides Logs SDK compliant with the specification.

The prototype was created in #4955.

Background

The goal is to design the exported API of the SDK would have low performance overhead. Most importantly, have a design that reduces the amount of heap allocations and even make it possible to have a zero-allocation implementation. Eliminating the amount of heap allocations reduces the GC pressure which can produce some of the largest improvements in performance.1

The main and recommended use case is to configure the SDK to use an OTLP exporter with a batch processor.2 Therefore, the implementation aims to be high-performant in this scenario. Some users that require high throughput may also want to use e.g. an user_events, LLTng or ETW exporter with a simple processor. Users may also want to use OTLP File or Standard Output exporter in order to emit logs to standard output/error or files.

Modules structure

The SDK is published as a single go.opentelemetry.io/otel/sdk/log Go module.

The exporters are going to be published as following Go modules:

  • go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc
  • go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp
  • go.opentelemetry.io/otel/exporters/stdout/stdoutlog

LoggerProvider

The LoggerProvider is implemented as LoggerProvider struct in provider.go.

LogRecord limits

The LogRecord limits can be configured using following options:

func WithAttributeCountLimit(limit int) LoggerProviderOption
func WithAttributeValueLengthLimit(limit int) LoggerProviderOption

The limits can be also configured using the OTEL_LOGRECORD_* environment variables as defined by the specification.

Processor

The LogRecordProcessor is defined as Processor interface in processor.go.

The user set processors for the LoggerProvider using func WithProcessor(processor Processor) LoggerProviderOption.

The user can configure custom processors and decorate built-in processors.

The specification may add new operations to the LogRecordProcessor. If it happens, CONTRIBUTING.md describes how the SDK can be extended in a backwards-compatible way.

SimpleProcessor

The Simple processor is implemented as SimpleProcessor struct in simple.go.

BatchProcessor

The Batching processor is implemented as BatchProcessor struct in batch.go.

The Batcher can be also configured using the OTEL_BLRP_* environment variables as defined by the specification.

Exporter

The LogRecordExporter is defined as Exporter interface in exporter.go.

The slice passed to Export must not be retained by the implementation (like e.g. io.Writer) so that the caller can reuse the passed slice (e.g. using sync.Pool) to avoid heap allocations on each call.

The specification may add new operations to the LogRecordExporter. If it happens, CONTRIBUTING.md describes how the SDK can be extended in a backwards-compatible way.

Record

The ReadWriteLogRecord is defined as Record struct in record.go.

The Record is designed similarly to log.Record in order to reduce the number of heap allocations when processing attributes.

The SDK does not have have an additional definition of ReadableLogRecord as the specification does not say that the exporters must not be able to modify the log records. It simply requires them to be able to read the log records. Having less abstractions reduces the API surface and makes the design simpler.

Benchmarking

The benchmarks are supposed to test end-to-end scenarios and avoid I/O that could affect the stability of the results.

The benchmark results can be found in the prototype.

Rejected alternatives

Represent both LogRecordProcessor and LogRecordExporter as Exporter

Because the LogRecordProcessor and the LogRecordProcessor abstractions are so similar, there was a proposal to unify them under single Exporter interface.3

However, introducing a Processor interface makes it easier to create custom processor decorators4 and makes the design more aligned with the specification.

Embed log.Record

Because Record and log.Record are very similar, there was a proposal to embed log.Record in Record definition.

log.Record supports only adding attributes. In the SDK, we also need to be able to modify the attributes (e.g. removal) provided via API.

Moreover it is safer to have these abstraction decoupled. E.g. there can be a need for some fields that can be set via API and cannot be modified by the processors.

Processor.OnEmit to accept Record values

There was a proposal to make the Processor's OnEmit to accept a Record value instead of a pointer to reduce allocations as well as to have design similar to slog.Handler.

There have been long discussions within the OpenTelemetry Specification SIG5 about whether such a design would comply with the specification. The summary was that the current processor design flaws are present in other languages as well. Therefore, it would be favorable to introduce new processing concepts (e.g. chaining processors) in the specification that would coexist with the current "mutable" processor design.

The performance disadvantages caused by using a pointer (which at the time of writing causes an additional heap allocation) may be mitigated by future versions of the Go compiler, thanks to improved escape analysis and profile-guided optimization (PGO)6.

On the other hand, Processor's Enabled is fine to accept a Record value as the processors should not mutate the passed parameters.