mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-03-25 21:28:58 +02:00
sdk/log: Implement Record (#5073)
This commit is contained in:
parent
54b6ee4174
commit
ca35244789
@ -3,17 +3,21 @@ module go.opentelemetry.io/otel/sdk/log
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.9.0
|
||||
go.opentelemetry.io/otel v1.24.0
|
||||
go.opentelemetry.io/otel/log v0.0.1-alpha
|
||||
go.opentelemetry.io/otel/sdk v1.24.0
|
||||
go.opentelemetry.io/otel/trace v1.24.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace go.opentelemetry.io/otel/metric => ../../metric
|
||||
|
@ -13,5 +13,7 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -4,6 +4,7 @@
|
||||
package log // import "go.opentelemetry.io/otel/sdk/log"
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/log"
|
||||
@ -12,130 +13,197 @@ import (
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// attributesInlineCount is the number of attributes that are efficiently
|
||||
// stored in an array within a Record. This value is borrowed from slog which
|
||||
// performed a quantitative survey of log library use and found this value to
|
||||
// cover 95% of all use-cases (https://go.dev/blog/slog#performance).
|
||||
const attributesInlineCount = 5
|
||||
|
||||
// Record is a log record emitted by the Logger.
|
||||
type Record struct{}
|
||||
type Record struct {
|
||||
// Do not embed the log.Record. Attributes need to be overwrite-able and
|
||||
// deep-copying needs to be possible.
|
||||
|
||||
timestamp time.Time
|
||||
observedTimestamp time.Time
|
||||
severity log.Severity
|
||||
severityText string
|
||||
body log.Value
|
||||
|
||||
// The fields below are for optimizing the implementation of Attributes and
|
||||
// AddAttributes. This design is borrowed from the slog Record type:
|
||||
// https://cs.opensource.google/go/go/+/refs/tags/go1.22.0:src/log/slog/record.go;l=20
|
||||
|
||||
// Allocation optimization: an inline array sized to hold
|
||||
// the majority of log calls (based on examination of open-source
|
||||
// code). It holds the start of the list of attributes.
|
||||
front [attributesInlineCount]log.KeyValue
|
||||
|
||||
// The number of attributes in front.
|
||||
nFront int
|
||||
|
||||
// The list of attributes except for those in front.
|
||||
// Invariants:
|
||||
// - len(back) > 0 if nFront == len(front)
|
||||
// - Unused array elements are zero-ed. Used to detect mistakes.
|
||||
back []log.KeyValue
|
||||
|
||||
traceID trace.TraceID
|
||||
spanID trace.SpanID
|
||||
traceFlags trace.TraceFlags
|
||||
|
||||
// resource represents the entity that collected the log.
|
||||
resource *resource.Resource
|
||||
|
||||
// scope is the Scope that the Logger was created with.
|
||||
scope *instrumentation.Scope
|
||||
|
||||
attributeValueLengthLimit int
|
||||
attributeCountLimit int
|
||||
}
|
||||
|
||||
// Timestamp returns the time when the log record occurred.
|
||||
func (r *Record) Timestamp() time.Time {
|
||||
// TODO (#5064): Implement.
|
||||
return time.Time{}
|
||||
return r.timestamp
|
||||
}
|
||||
|
||||
// SetTimestamp sets the time when the log record occurred.
|
||||
func (r *Record) SetTimestamp(t time.Time) {
|
||||
// TODO (#5064): Implement.
|
||||
r.timestamp = t
|
||||
}
|
||||
|
||||
// ObservedTimestamp returns the time when the log record was observed.
|
||||
func (r *Record) ObservedTimestamp() time.Time {
|
||||
// TODO (#5064): Implement.
|
||||
return time.Time{}
|
||||
return r.observedTimestamp
|
||||
}
|
||||
|
||||
// SetObservedTimestamp sets the time when the log record was observed.
|
||||
func (r *Record) SetObservedTimestamp(t time.Time) {
|
||||
// TODO (#5064): Implement.
|
||||
r.observedTimestamp = t
|
||||
}
|
||||
|
||||
// Severity returns the severity of the log record.
|
||||
func (r *Record) Severity() log.Severity {
|
||||
// TODO (#5064): Implement.
|
||||
return log.Severity(0)
|
||||
return r.severity
|
||||
}
|
||||
|
||||
// SetSeverity sets the severity level of the log record.
|
||||
func (r *Record) SetSeverity(level log.Severity) {
|
||||
// TODO (#5064): Implement.
|
||||
r.severity = level
|
||||
}
|
||||
|
||||
// SeverityText returns severity (also known as log level) text. This is the
|
||||
// original string representation of the severity as it is known at the source.
|
||||
func (r *Record) SeverityText() string {
|
||||
// TODO (#5064): Implement.
|
||||
return ""
|
||||
return r.severityText
|
||||
}
|
||||
|
||||
// SetSeverityText sets severity (also known as log level) text. This is the
|
||||
// original string representation of the severity as it is known at the source.
|
||||
func (r *Record) SetSeverityText(text string) {
|
||||
// TODO (#5064): Implement.
|
||||
r.severityText = text
|
||||
}
|
||||
|
||||
// Body returns the body of the log record.
|
||||
func (r *Record) Body() log.Value {
|
||||
// TODO (#5064): Implement.
|
||||
return log.Value{}
|
||||
return r.body
|
||||
}
|
||||
|
||||
// SetBody sets the body of the log record.
|
||||
func (r *Record) SetBody(v log.Value) {
|
||||
// TODO (#5064): Implement.
|
||||
r.body = v
|
||||
}
|
||||
|
||||
// WalkAttributes walks all attributes the log record holds by calling f for
|
||||
// each on each [log.KeyValue] in the [Record]. Iteration stops if f returns false.
|
||||
func (r *Record) WalkAttributes(f func(log.KeyValue) bool) {
|
||||
// TODO (#5064): Implement.
|
||||
for i := 0; i < r.nFront; i++ {
|
||||
if !f(r.front[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, a := range r.back {
|
||||
if !f(a) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddAttributes adds attributes to the log record.
|
||||
func (r *Record) AddAttributes(attrs ...log.KeyValue) {
|
||||
// TODO (#5064): Implement.
|
||||
var i int
|
||||
for i = 0; i < len(attrs) && r.nFront < len(r.front); i++ {
|
||||
a := attrs[i]
|
||||
r.front[r.nFront] = a
|
||||
r.nFront++
|
||||
}
|
||||
|
||||
r.back = slices.Grow(r.back, len(attrs[i:]))
|
||||
r.back = append(r.back, attrs[i:]...)
|
||||
}
|
||||
|
||||
// SetAttributes sets (and overrides) attributes to the log record.
|
||||
func (r *Record) SetAttributes(attrs ...log.KeyValue) {
|
||||
// TODO (#5064): Implement.
|
||||
r.nFront = 0
|
||||
var i int
|
||||
for i = 0; i < len(attrs) && r.nFront < len(r.front); i++ {
|
||||
a := attrs[i]
|
||||
r.front[r.nFront] = a
|
||||
r.nFront++
|
||||
}
|
||||
|
||||
r.back = slices.Clone(attrs[i:])
|
||||
}
|
||||
|
||||
// AttributesLen returns the number of attributes in the log record.
|
||||
func (r *Record) AttributesLen() int {
|
||||
// TODO (#5064): Implement.
|
||||
return 0
|
||||
return r.nFront + len(r.back)
|
||||
}
|
||||
|
||||
// TraceID returns the trace ID or empty array.
|
||||
func (r *Record) TraceID() trace.TraceID {
|
||||
// TODO (#5064): Implement.
|
||||
return trace.TraceID{}
|
||||
return r.traceID
|
||||
}
|
||||
|
||||
// SetTraceID sets the trace ID.
|
||||
func (r *Record) SetTraceID(id trace.TraceID) {
|
||||
// TODO (#5064): Implement.
|
||||
r.traceID = id
|
||||
}
|
||||
|
||||
// SpanID returns the span ID or empty array.
|
||||
func (r *Record) SpanID() trace.SpanID {
|
||||
// TODO (#5064): Implement.
|
||||
return trace.SpanID{}
|
||||
return r.spanID
|
||||
}
|
||||
|
||||
// SetSpanID sets the span ID.
|
||||
func (r *Record) SetSpanID(id trace.SpanID) {
|
||||
// TODO (#5064): Implement.
|
||||
r.spanID = id
|
||||
}
|
||||
|
||||
// TraceFlags returns the trace flags.
|
||||
func (r *Record) TraceFlags() trace.TraceFlags {
|
||||
return 0
|
||||
return r.traceFlags
|
||||
}
|
||||
|
||||
// SetTraceFlags sets the trace flags.
|
||||
func (r *Record) SetTraceFlags(flags trace.TraceFlags) {
|
||||
// TODO (#5064): Implement.
|
||||
r.traceFlags = flags
|
||||
}
|
||||
|
||||
// Resource returns the entity that collected the log.
|
||||
func (r *Record) Resource() resource.Resource {
|
||||
// TODO (#5064): Implement.
|
||||
return resource.Resource{}
|
||||
if r.resource == nil {
|
||||
return *resource.Empty()
|
||||
}
|
||||
return *r.resource
|
||||
}
|
||||
|
||||
// InstrumentationScope returns the scope that the Logger was created with.
|
||||
func (r *Record) InstrumentationScope() instrumentation.Scope {
|
||||
// TODO (#5064): Implement.
|
||||
return instrumentation.Scope{}
|
||||
if r.scope == nil {
|
||||
return instrumentation.Scope{}
|
||||
}
|
||||
return *r.scope
|
||||
}
|
||||
|
||||
// AttributeValueLengthLimit is the maximum allowed attribute value length.
|
||||
@ -145,8 +213,7 @@ func (r *Record) InstrumentationScope() instrumentation.Scope {
|
||||
//
|
||||
// Negative value means no limit should be applied.
|
||||
func (r *Record) AttributeValueLengthLimit() int {
|
||||
// TODO (#5064): Implement.
|
||||
return 0
|
||||
return r.attributeValueLengthLimit
|
||||
}
|
||||
|
||||
// AttributeCountLimit is the maximum allowed log record attribute count. Any
|
||||
@ -156,13 +223,13 @@ func (r *Record) AttributeValueLengthLimit() int {
|
||||
//
|
||||
// Negative value means no limit should be applied.
|
||||
func (r *Record) AttributeCountLimit() int {
|
||||
// TODO (#5064): Implement.
|
||||
return 0
|
||||
return r.attributeCountLimit
|
||||
}
|
||||
|
||||
// Clone returns a copy of the record with no shared state. The original record
|
||||
// and the clone can both be modified without interfering with each other.
|
||||
func (r *Record) Clone() Record {
|
||||
// TODO (#5064): Implement.
|
||||
return *r
|
||||
res := *r
|
||||
res.back = slices.Clone(r.back)
|
||||
return res
|
||||
}
|
||||
|
206
sdk/log/record_test.go
Normal file
206
sdk/log/record_test.go
Normal file
@ -0,0 +1,206 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/log"
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
func TestRecordTimestamp(t *testing.T) {
|
||||
now := time.Now()
|
||||
r := new(Record)
|
||||
r.SetTimestamp(now)
|
||||
assert.Equal(t, now, r.Timestamp())
|
||||
}
|
||||
|
||||
func TestRecordObservedTimestamp(t *testing.T) {
|
||||
now := time.Now()
|
||||
r := new(Record)
|
||||
r.SetObservedTimestamp(now)
|
||||
assert.Equal(t, now, r.ObservedTimestamp())
|
||||
}
|
||||
|
||||
func TestRecordSeverity(t *testing.T) {
|
||||
s := log.SeverityInfo
|
||||
r := new(Record)
|
||||
r.SetSeverity(s)
|
||||
assert.Equal(t, s, r.Severity())
|
||||
}
|
||||
|
||||
func TestRecordSeverityText(t *testing.T) {
|
||||
text := "text"
|
||||
r := new(Record)
|
||||
r.SetSeverityText(text)
|
||||
assert.Equal(t, text, r.SeverityText())
|
||||
}
|
||||
|
||||
func TestRecordBody(t *testing.T) {
|
||||
v := log.BoolValue(true)
|
||||
r := new(Record)
|
||||
r.SetBody(v)
|
||||
assert.True(t, v.Equal(r.Body()))
|
||||
}
|
||||
|
||||
func TestRecordAttributes(t *testing.T) {
|
||||
attrs := []log.KeyValue{
|
||||
log.Bool("0", true),
|
||||
log.Int64("1", 2),
|
||||
log.Float64("2", 3.0),
|
||||
log.String("3", "forth"),
|
||||
log.Slice("4", log.Int64Value(1)),
|
||||
log.Map("5", log.Int("key", 2)),
|
||||
log.Bytes("6", []byte("six")),
|
||||
}
|
||||
r := new(Record)
|
||||
r.SetAttributes(attrs...)
|
||||
r.SetAttributes(attrs[:2]...) // Overwrite existing.
|
||||
r.AddAttributes(attrs[2:]...)
|
||||
|
||||
assert.Equal(t, len(attrs), r.AttributesLen(), "attribute length")
|
||||
|
||||
for n := range attrs {
|
||||
var i int
|
||||
r.WalkAttributes(func(log.KeyValue) bool {
|
||||
i++
|
||||
return i <= n
|
||||
})
|
||||
assert.Equalf(t, n+1, i, "WalkAttributes did not stop at %d", n+1)
|
||||
}
|
||||
|
||||
var i int
|
||||
r.WalkAttributes(func(kv log.KeyValue) bool {
|
||||
assert.Truef(t, kv.Equal(attrs[i]), "%d: %v != %v", i, kv, attrs[i])
|
||||
i++
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func TestRecordTraceID(t *testing.T) {
|
||||
id := trace.TraceID([16]byte{1})
|
||||
r := new(Record)
|
||||
r.SetTraceID(id)
|
||||
assert.Equal(t, id, r.TraceID())
|
||||
}
|
||||
|
||||
func TestRecordSpanID(t *testing.T) {
|
||||
id := trace.SpanID([8]byte{1})
|
||||
r := new(Record)
|
||||
r.SetSpanID(id)
|
||||
assert.Equal(t, id, r.SpanID())
|
||||
}
|
||||
|
||||
func TestRecordTraceFlags(t *testing.T) {
|
||||
flag := trace.FlagsSampled
|
||||
r := new(Record)
|
||||
r.SetTraceFlags(flag)
|
||||
assert.Equal(t, flag, r.TraceFlags())
|
||||
}
|
||||
|
||||
func TestRecordResource(t *testing.T) {
|
||||
r := new(Record)
|
||||
assert.NotPanics(t, func() { r.Resource() })
|
||||
|
||||
res := resource.NewSchemaless(attribute.Bool("key", true))
|
||||
r.resource = res
|
||||
got := r.Resource()
|
||||
assert.True(t, res.Equal(&got))
|
||||
}
|
||||
|
||||
func TestRecordInstrumentationScope(t *testing.T) {
|
||||
r := new(Record)
|
||||
assert.NotPanics(t, func() { r.InstrumentationScope() })
|
||||
|
||||
scope := instrumentation.Scope{Name: "testing"}
|
||||
r.scope = &scope
|
||||
assert.Equal(t, scope, r.InstrumentationScope())
|
||||
}
|
||||
|
||||
func TestRecordAttributeValueLengthLimit(t *testing.T) {
|
||||
limit := 12
|
||||
r := new(Record)
|
||||
r.attributeValueLengthLimit = limit
|
||||
assert.Equal(t, limit, r.AttributeValueLengthLimit())
|
||||
}
|
||||
|
||||
func TestRecordAttributeCountLimit(t *testing.T) {
|
||||
limit := 21
|
||||
r := new(Record)
|
||||
r.attributeCountLimit = limit
|
||||
assert.Equal(t, limit, r.AttributeCountLimit())
|
||||
}
|
||||
|
||||
func TestRecordClone(t *testing.T) {
|
||||
now0 := time.Now()
|
||||
sev0 := log.SeverityInfo
|
||||
text0 := "text"
|
||||
val0 := log.BoolValue(true)
|
||||
attr0 := log.Bool("0", true)
|
||||
traceID0 := trace.TraceID([16]byte{1})
|
||||
spanID0 := trace.SpanID([8]byte{1})
|
||||
flag0 := trace.FlagsSampled
|
||||
|
||||
r0 := new(Record)
|
||||
r0.SetTimestamp(now0)
|
||||
r0.SetObservedTimestamp(now0)
|
||||
r0.SetSeverity(sev0)
|
||||
r0.SetSeverityText(text0)
|
||||
r0.SetBody(val0)
|
||||
r0.SetAttributes(attr0)
|
||||
r0.SetTraceID(traceID0)
|
||||
r0.SetSpanID(spanID0)
|
||||
r0.SetTraceFlags(flag0)
|
||||
|
||||
now1 := now0.Add(time.Second)
|
||||
sev1 := log.SeverityDebug
|
||||
text1 := "string"
|
||||
val1 := log.IntValue(1)
|
||||
attr1 := log.Int64("1", 2)
|
||||
traceID1 := trace.TraceID([16]byte{2})
|
||||
spanID1 := trace.SpanID([8]byte{2})
|
||||
flag1 := trace.TraceFlags(2)
|
||||
|
||||
r1 := r0.Clone()
|
||||
r1.SetTimestamp(now1)
|
||||
r1.SetObservedTimestamp(now1)
|
||||
r1.SetSeverity(sev1)
|
||||
r1.SetSeverityText(text1)
|
||||
r1.SetBody(val1)
|
||||
r1.SetAttributes(attr1)
|
||||
r1.SetTraceID(traceID1)
|
||||
r1.SetSpanID(spanID1)
|
||||
r1.SetTraceFlags(flag1)
|
||||
|
||||
assert.Equal(t, now0, r0.Timestamp())
|
||||
assert.Equal(t, now0, r0.ObservedTimestamp())
|
||||
assert.Equal(t, sev0, r0.Severity())
|
||||
assert.Equal(t, text0, r0.SeverityText())
|
||||
assert.True(t, val0.Equal(r0.Body()))
|
||||
assert.Equal(t, traceID0, r0.TraceID())
|
||||
assert.Equal(t, spanID0, r0.SpanID())
|
||||
assert.Equal(t, flag0, r0.TraceFlags())
|
||||
r0.WalkAttributes(func(kv log.KeyValue) bool {
|
||||
return assert.Truef(t, kv.Equal(attr0), "%v != %v", kv, attr0)
|
||||
})
|
||||
|
||||
assert.Equal(t, now1, r1.Timestamp())
|
||||
assert.Equal(t, now1, r1.ObservedTimestamp())
|
||||
assert.Equal(t, sev1, r1.Severity())
|
||||
assert.Equal(t, text1, r1.SeverityText())
|
||||
assert.True(t, val1.Equal(r1.Body()))
|
||||
assert.Equal(t, traceID1, r1.TraceID())
|
||||
assert.Equal(t, spanID1, r1.SpanID())
|
||||
assert.Equal(t, flag1, r1.TraceFlags())
|
||||
r1.WalkAttributes(func(kv log.KeyValue) bool {
|
||||
return assert.Truef(t, kv.Equal(attr1), "%v != %v", kv, attr1)
|
||||
})
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user