Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logs Bridge API prototype #4725

Closed
wants to merge 107 commits into from
Closed
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
a6f19fe
Add empty design doc
pellared Nov 17, 2023
c8fcf28
Init Logs Bridge API design
pellared Nov 17, 2023
e48ae14
Document the design
pellared Nov 17, 2023
31c755f
Merge branch 'main' into logs-design
pellared Nov 20, 2023
cd39d63
Add Rationale
pellared Nov 20, 2023
4578694
Move design doc closer to the code
pellared Nov 20, 2023
fbd656f
Cleanup
pellared Nov 20, 2023
9d9f10b
Cleanup
pellared Nov 20, 2023
55bad1e
Fix typo
pellared Nov 20, 2023
249f4ed
Apply suggestions from code review
pellared Nov 20, 2023
33a8aca
Clairfy RecordOption
pellared Nov 20, 2023
2e3587c
document fields that optimize attrs
pellared Nov 21, 2023
f19f0c8
Add missing parenthesis
pellared Nov 21, 2023
5456b9d
Record and NewRecord is needed for SDK
pellared Nov 21, 2023
4365860
Add usage examples
pellared Nov 21, 2023
892ee8f
Document Rejected Alternative: Reuse slog
pellared Nov 21, 2023
f288cf2
Logger.Emit with record as argument
pellared Nov 22, 2023
c0a795b
Document Logger.Emit extensibility
pellared Nov 22, 2023
2704436
Apply suggestions from code review
pellared Nov 24, 2023
a718f69
Write prototype implementation and benchmarks
pellared Nov 24, 2023
700c91f
Add missing vanity import
pellared Nov 24, 2023
2e8403e
gofumpt
pellared Nov 24, 2023
441cde8
Add TODO comments
pellared Nov 24, 2023
de09ce6
Add copywrite header and fix for Go 1.20
pellared Nov 24, 2023
dad0503
refactor
pellared Nov 24, 2023
c43bd51
Update design
pellared Nov 24, 2023
19438ca
Update design
pellared Nov 24, 2023
d4896f6
Rejected Alternative: Passing struct as parameter to LoggerProvider.L…
pellared Nov 24, 2023
cf10dd0
Merge branch 'main' into logs-design
pellared Nov 28, 2023
ddcdbda
Add go.opentelemetry.io/otel/log/benchmark module
pellared Nov 28, 2023
ca5bfb7
Add slog.Handler naive implementation
pellared Nov 28, 2023
21ca157
go mod tidy
pellared Nov 28, 2023
0d9c335
Add logr.LogSink naive implementation
pellared Nov 28, 2023
1c3776b
Update design
pellared Nov 28, 2023
5bb9801
gofumpt
pellared Nov 28, 2023
7084df2
Refactor slogHandler
pellared Nov 28, 2023
3b8a68c
Export AttributesInlineCount
pellared Nov 28, 2023
44e01f8
Merge branch 'main' into logs-design
pellared Nov 30, 2023
e7b8c7a
Rename Record.Attributes to Record.WalkAttributes
pellared Nov 30, 2023
d19ebad
Merge branch 'main' into logs-design
pellared Dec 4, 2023
ba2903a
Unexport AttributesInlineCount
pellared Dec 4, 2023
6443d19
Fix typo
pellared Dec 4, 2023
b170245
Merge branch 'main' into logs-design
pellared Dec 5, 2023
21bea0f
Ignore request cancelation
pellared Dec 5, 2023
c8b3f05
Encapsulate Record fields
pellared Dec 5, 2023
a8ba6be
Fix tests
pellared Dec 5, 2023
60a4e45
Merge branch 'main' into logs-design
pellared Dec 7, 2023
b60433a
Merge branch 'main' into logs-design
pellared Dec 11, 2023
689faff
fix typo
pellared Dec 11, 2023
7f299ff
Merge branch 'main' into logs-design
pellared Dec 15, 2023
9f9acaf
Rejected Alternative: Logger.WithAttributes
pellared Dec 15, 2023
9deedde
Fix MD009/no-trailing-spaces
pellared Dec 15, 2023
ecb503a
Merge branch 'main' into logs-design
pellared Dec 18, 2023
445bae1
Make parallel benchmarks
pellared Dec 18, 2023
cd5a898
Merge branch 'main' into logs-design
pellared Dec 19, 2023
cd8217a
Merge branch 'main' into logs-design
pellared Dec 19, 2023
977f3d7
Record attributes as field and use sync.Pool for reducing allocations
pellared Dec 22, 2023
2d161ca
Merge branch 'main' into logs-design
pellared Dec 22, 2023
11e232c
Merge branch 'main' into logs-design
pellared Dec 27, 2023
033e2e9
go mod tidy
pellared Dec 27, 2023
8c8f7ab
Trace contextg correlation design
pellared Dec 27, 2023
a79b08f
writerLogger and slogHandler handle trace context
pellared Dec 27, 2023
4be490b
go mod tidy
pellared Dec 27, 2023
3552368
logrSink handle trace context
pellared Dec 27, 2023
3fbebee
Refine API implementation section
pellared Dec 27, 2023
8e25e81
Fix grammar
pellared Dec 27, 2023
321a049
Move log/benchmark to log/internal
pellared Dec 27, 2023
2d62729
Merge branch 'main' into logs-design
pellared Jan 10, 2024
a0bc4b8
Update log package
pellared Jan 10, 2024
f6a2308
go mod tidy
pellared Jan 10, 2024
b780472
Remove DESIGN.md
pellared Jan 10, 2024
8eb2fab
Merge branch 'main' into logs-design
pellared Jan 16, 2024
d832817
Add Severity[Level]1 consts
pellared Jan 17, 2024
f78cbec
Record attributes based on slog.Record (#6)
pellared Jan 18, 2024
1a8b1c5
Merge branch 'main' into logs-design
pellared Jan 18, 2024
6730f06
go mod tidy
pellared Jan 18, 2024
d114464
The caller must not subsequently mutate the record passed to Emit
pellared Jan 18, 2024
f08ebbf
Refine Emit docs
pellared Jan 18, 2024
d665181
Merge branch 'main' into logs-design
pellared Jan 18, 2024
a116609
go mod tidy
pellared Jan 18, 2024
5d8c58f
Handle structured body and attributes (#7)
pellared Jan 24, 2024
3131463
Merge branch 'main' into logs-design
pellared Jan 25, 2024
ecc26bc
Record with pointer receivers only (#8)
pellared Jan 26, 2024
2778c55
As[Type] Value methods
pellared Jan 26, 2024
68d99a8
Use stringer for enum types
pellared Jan 26, 2024
e5dc7cf
Revert Makefile changes
pellared Jan 26, 2024
4b96c4d
Merge branch 'main' into logs-design
pellared Jan 29, 2024
4084475
Merge branch 'main' into logs-design
pellared Jan 30, 2024
d4d4274
Fix Severity.String
pellared Jan 30, 2024
91358b4
TestKind
pellared Jan 30, 2024
b90eca9
Refactor Value.String
pellared Jan 30, 2024
a02b8ea
Remove KeyValue.Invalid
pellared Feb 1, 2024
a8a1b7e
Update AddAttributes
pellared Feb 1, 2024
c4a2209
Remove header
pellared Feb 1, 2024
45cfdcb
Update comment
pellared Feb 1, 2024
b55e22e
Refine comment
pellared Feb 1, 2024
aa3b222
Merge branch 'main' into logs-design
pellared Feb 1, 2024
4dccab0
go mod tidy
pellared Feb 1, 2024
4e2e5e3
Merge branch 'logs-design' of https://github.com/pellared/opentelemet…
pellared Feb 1, 2024
a8f265c
Refactor Severity to not use iota
pellared Feb 1, 2024
f8c46f1
Merge branch 'main' into logs-design
pellared Feb 6, 2024
b996e00
Remove Record.Clone as it is not necessary
pellared Feb 7, 2024
ed67f01
Refine KeyValue docs
pellared Feb 7, 2024
5851ead
Remove unused attrsSlice
pellared Feb 7, 2024
273a964
Merge branch 'main' into logs-design
pellared Feb 7, 2024
e89444d
go mod tidy
pellared Feb 7, 2024
147762f
value does not panic and change List to Slice
pellared Feb 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,15 @@ updates:
schedule:
interval: weekly
day: sunday
- package-ecosystem: gomod
directory: /log
labels:
- dependencies
- go
- Skip Changelog
schedule:
interval: weekly
day: sunday
- package-ecosystem: gomod
directory: /metric
labels:
Expand Down
234 changes: 234 additions & 0 deletions log/DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# Logs Bridge API

OpenTelemetry Logs tracking issue at [#4696](https://github.com/open-telemetry/opentelemetry-go/issues/4696).

## Abstract

We propose adding a `go.opentelemetry.io/otel/log` Go module which will provide
[Logs Bridge API](https://opentelemetry.io/docs/specs/otel/logs/bridge-api/).

## Background

The key challenge is to create a well-performant API compliant with the specification.
Performance is seen as one of the most important characteristics of logging libraries in Go.

## Design

This proposed design aims to:

- be specification compliant,
- have similar API to Trace and Metrics API,
- take advantage of both OpenTelemetry and `slog` experience to achieve acceptable performance.

### Module structure

The Go module consits of the following packages:

- `go.opentelemetry.io/otel/log`
- `go.opentelemetry.io/otel/log/embedded`
- `go.opentelemetry.io/otel/log/noop`

### LoggerProvider

The [`LoggerProvider` abstraction](https://opentelemetry.io/docs/specs/otel/logs/bridge-api/#loggerprovider)
is defined as an interface [provider.go](provider.go).

### Logger

The [`Logger` abstraction](https://opentelemetry.io/docs/specs/otel/logs/bridge-api/#logger)
is defined as an interface in [logger.go](logger.go).

### Record

The [`LogRecord` abstraction](https://opentelemetry.io/docs/specs/otel/logs/bridge-api/#logger)
is defined as a struct in [record.go](record.go).

`Record` has `Attributes` and `AddAttributes` methods,
like [`slog.Record.Attrs`](https://pkg.go.dev/log/slog#Record.Attrs)
and [`slog.Record.AddAttrs`](https://pkg.go.dev/log/slog#Record.AddAttrs),
in order to achieve high-performance when accessing and setting attributes efficiently.

`Record` has a `AttributesLen` method that returns
the number of attributes to allow slice preallocation
when converting records to a different representation.

### Usage Example: Log Bridge implementation

Excerpt of a [slog.Handler](https://pkg.go.dev/log/slog#Handler)
naive implementation.

```go
type handler struct {
logger log.Logger
}

func (h *handler) Handle(ctx context.Context, r slog.Record) error {
lvl := convertLevel(r.Level)

record := Record{Timestamp: r.Time, Severity: lvl, Body: r.Message}

if r.AttributesLen() > 5 {
pellared marked this conversation as resolved.
Show resolved Hide resolved
attrs := make([]attribute.KeyValue, 0, len(r.AttributesLen()))
r.Attrs(func(a slog.Attr) bool {
attrs = append(attrs, convertAttr(a))
return true
})
record.AddAttributes(attrs...)
} else {
// special case that avoids heap allocations (hot path)
r.Attrs(func(a slog.Attr) bool {
record.AddAttributes(convertAttr(a))
return true
})
}

h.logger.Emit(ctx, record)
return nil
}
```

### Usage Example: Direct API usage

The users may also chose to use the API directly.

```go
package app

var logger = otel.Logger("my-service")

// In some function:
logger.Emit(ctx, Record{Severity: log.SeverityInfo, Body: "Application started."})
```

### Usage Example: SDK implementation

Excerpt of how SDK can implement the `Logger` interface.

```go
type Logger struct {
scope instrumentation.Scope
processor Processor
}

func (l *Logger) Emit(ctx context.Context, r log.Record) {
// Create log record model.
record, err := toModel(r)
if err != nil {
otel.Handle(err)
return
}
l.processor.Process(ctx, record)
}
```

## Compatibility

The backwards compatibility is achieved using the `embedded` design pattern
that is already used in Trace API and Metrics API.

Additionally, the `Logger.Emit` functionality can be extended by
adding new exported fields and methods to the `Record` struct.

## Benchmarking

The benchmarks takes inspiration from [`slog`](https://pkg.go.dev/log/slog),
pellared marked this conversation as resolved.
Show resolved Hide resolved
because for the Go team it was also critical to create API that would be fast
and interoperable with existing logging packages.[^1][^2]

## Rationale

### Rejected Alternative: Reuse slog

The API must not be coupled to [`slog`](https://pkg.go.dev/log/slog),
nor any other logging library.

The API needs to evolve orthogonally to `slog`.

`slog` is not compliant with the [Logs Bridge API](https://opentelemetry.io/docs/specs/otel/logs/bridge-api/).
and we cannot expect the Go team to make `slog` compliant with it.

The interoperabilty can be achieved using [a log bridge](https://opentelemetry.io/docs/specs/otel/glossary/#log-appender--bridge).

You can read more about OpenTelemetry Logs design on [opentelemetry.io](https://opentelemetry.io/docs/concepts/signals/logs/).

### Rejected Alternative: Record as interface

`Record` is defined as a `struct` because of the following reasons.

Log record is a value object without any behavior.
It is used as data input for Logger methods.

The log record resembles the instrument config structs like [metric.Float64CounterConfig](https://pkg.go.dev/go.opentelemetry.io/otel/metric#Float64CounterConfig).

Using `struct` instead of `interface` should have better the performance as e.g.
indirect calls are less optimized,
usage of interfaces tend to increase heap allocations.[^2]

The `Record` design is inspired by [`slog.Record`](https://pkg.go.dev/log/slog#Record).

### Rejected Alternative: Options as parameter to Logger.Emit

One of the initial ideas was to have:

```go
type Logger interface{
embedded.Logger
Emit(ctx context.Context, options ...RecordOption)
}
```

The main reason was that design would be similar
to the [Meter API](https://pkg.go.dev/go.opentelemetry.io/otel/metric#Meter)
for creating instruments.

However, passing `Record` directly, instead of using options,
is more performant as it reduces heap allocations.[^3]

Another advantage of passing `Record` is that API would not have functions like `NewRecord(options...)`,
which would be used by the SDK and not by the users.

At last, the definition would be similar to [`slog.Handler.Handle`](https://pkg.go.dev/log/slog#Handler)
that was designed to provide optimization opportunities.[^1]

### Rejected Alternative: Passing record as pointer to Logger.Emit

So far the benchmarks do not show differences that would
favor passing the record via pointer (and vice versa).

Passing via value feels safer because of the following reasons.

It follows the design of [`slog.Handler`](https://pkg.go.dev/log/slog#Handler).

It should reduce the possibility of a heap allocation.

The user would not be able to pass `nil`.
Therefore, it reduces the possiblity to have a nil pointer dereference.

### Rejected Alternative: Passing struct as parameter to LoggerProvider.Logger

Similarly to `Logger.Emit`, we could have something like:

```go
type Logger interface{
embedded.Logger
Logger(name context.Context, config LoggerConfig)
}
```

The drawback of this idea would be that this would be
a different design from Trace and Metrics API.

The performance of acquiring a logger is not as critical
as the performance of emitting a log record. While a single
HTTP/RPC handler could write hundreds of logs, it should not
create a new logger for each log entry.
The application should reuse loggers whenever possible.

## Open issues (if applicable)

<!-- A discussion of issues relating to this proposal for which the author does not
know the solution. This section may be omitted if there are none. -->

[^1]: Jonathan Amsterdam, [The Go Blog: Structured Logging with slog](https://go.dev/blog/slog)
[^2]: Jonathan Amsterdam, [GopherCon Europe 2023: A Fast Structured Logging Package](https://www.youtube.com/watch?v=tC4Jt3i62ns)
[^3]: [Emit definition discussion with benchmarks](https://github.com/open-telemetry/opentelemetry-go/pull/4725#discussion_r1400869566)
Loading