You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2026-06-03 18:35:08 +02:00
Implement the log Record type (#4939)
This commit is contained in:
+103
-14
@@ -16,47 +16,136 @@ package log // import "go.opentelemetry.io/otel/log"
|
||||
|
||||
import "time"
|
||||
|
||||
// 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 represents a log record.
|
||||
type Record struct{} // TODO (#4913): implement.
|
||||
type Record struct {
|
||||
timestamp time.Time
|
||||
observedTimestamp time.Time
|
||||
severity Severity
|
||||
severityText string
|
||||
body 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]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 []KeyValue
|
||||
}
|
||||
|
||||
// Timestamp returns the time when the log record occurred.
|
||||
func (r *Record) Timestamp() time.Time { return time.Time{} } // TODO (#4913): implement.
|
||||
func (r *Record) Timestamp() time.Time {
|
||||
return r.timestamp
|
||||
}
|
||||
|
||||
// SetTimestamp sets the time when the log record occurred.
|
||||
func (r *Record) SetTimestamp(t time.Time) {} // TODO (#4913): implement.
|
||||
func (r *Record) SetTimestamp(t time.Time) {
|
||||
r.timestamp = t
|
||||
}
|
||||
|
||||
// ObservedTimestamp returns the time when the log record was observed.
|
||||
func (r *Record) ObservedTimestamp() time.Time { return time.Time{} } // TODO (#4913): implement.
|
||||
func (r *Record) ObservedTimestamp() time.Time {
|
||||
return r.observedTimestamp
|
||||
}
|
||||
|
||||
// SetObservedTimestamp sets the time when the log record was observed.
|
||||
func (r *Record) SetObservedTimestamp(t time.Time) {} // TODO (#4913): implement.
|
||||
func (r *Record) SetObservedTimestamp(t time.Time) {
|
||||
r.observedTimestamp = t
|
||||
}
|
||||
|
||||
// Severity returns the [Severity] of the log record.
|
||||
func (r *Record) Severity() Severity { return 0 } // TODO (#4913): implement.
|
||||
func (r *Record) Severity() Severity {
|
||||
return r.severity
|
||||
}
|
||||
|
||||
// SetSeverity sets the [Severity] level of the log record.
|
||||
func (r *Record) SetSeverity(level Severity) {} // TODO (#4913): implement.
|
||||
func (r *Record) SetSeverity(level Severity) {
|
||||
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 { return "" } // TODO (#4913): implement.
|
||||
func (r *Record) SeverityText() string {
|
||||
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 (#4913): implement.
|
||||
func (r *Record) SetSeverityText(text string) {
|
||||
r.severityText = text
|
||||
}
|
||||
|
||||
// Body returns the body of the log record.
|
||||
func (r *Record) Body() Value { return Value{} } // TODO (#4913): implement.
|
||||
func (r *Record) Body() Value {
|
||||
return r.body
|
||||
}
|
||||
|
||||
// SetBody sets the body of the log record.
|
||||
func (r *Record) SetBody(v Value) {} // TODO (#4913): implement.
|
||||
func (r *Record) SetBody(v Value) {
|
||||
r.body = v
|
||||
}
|
||||
|
||||
// WalkAttributes walks all attributes the log record holds by calling f for
|
||||
// each on each [KeyValue] in the [Record]. Iteration stops if f returns false.
|
||||
func (r *Record) WalkAttributes(f func(KeyValue) bool) {} // TODO (#4913): implement.
|
||||
func (r *Record) WalkAttributes(f func(KeyValue) bool) {
|
||||
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(attributes ...KeyValue) {} // TODO (#4913): implement.
|
||||
func (r *Record) AddAttributes(attrs ...KeyValue) {
|
||||
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++
|
||||
}
|
||||
|
||||
// TODO: when Go 1.20 is no longer supported, use slices.Grow instead.
|
||||
r.back = grow(r.back, len(attrs[i:]))
|
||||
r.back = append(r.back, attrs[i:]...)
|
||||
}
|
||||
|
||||
// grow increases the slice's capacity, if necessary, to guarantee space for
|
||||
// another n elements. After grow(n), at least n elements can be appended to
|
||||
// the slice without another allocation.
|
||||
//
|
||||
// This is based on [Grow]. It is not available in Go 1.20 so it is reproduced
|
||||
// here.
|
||||
//
|
||||
// [Grow]: https://pkg.go.dev/slices#Grow
|
||||
func grow(slice []KeyValue, n int) []KeyValue {
|
||||
if n -= cap(slice) - len(slice); n > 0 {
|
||||
slice = append(slice[:cap(slice)], make([]KeyValue, n)...)[:len(slice)]
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// AttributesLen returns the number of attributes in the log record.
|
||||
func (r *Record) AttributesLen() int { return 0 } // TODO (#4913): implement.
|
||||
func (r *Record) AttributesLen() int {
|
||||
return r.nFront + len(r.back)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package log_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.opentelemetry.io/otel/log"
|
||||
)
|
||||
|
||||
var y2k = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
func TestRecordTimestamp(t *testing.T) {
|
||||
var r log.Record
|
||||
r.SetTimestamp(y2k)
|
||||
assert.Equal(t, y2k, r.Timestamp())
|
||||
}
|
||||
|
||||
func TestRecordObservedTimestamp(t *testing.T) {
|
||||
var r log.Record
|
||||
r.SetObservedTimestamp(y2k)
|
||||
assert.Equal(t, y2k, r.ObservedTimestamp())
|
||||
}
|
||||
|
||||
func TestRecordSeverity(t *testing.T) {
|
||||
var r log.Record
|
||||
r.SetSeverity(log.SeverityInfo)
|
||||
assert.Equal(t, log.SeverityInfo, r.Severity())
|
||||
}
|
||||
|
||||
func TestRecordSeverityText(t *testing.T) {
|
||||
const text = "testing text"
|
||||
|
||||
var r log.Record
|
||||
r.SetSeverityText(text)
|
||||
assert.Equal(t, text, r.SeverityText())
|
||||
}
|
||||
|
||||
func TestRecordBody(t *testing.T) {
|
||||
body := log.StringValue("testing body value")
|
||||
|
||||
var r log.Record
|
||||
r.SetBody(body)
|
||||
assert.Equal(t, body, r.Body())
|
||||
}
|
||||
|
||||
func TestRecordAttributes(t *testing.T) {
|
||||
attrs := []log.KeyValue{
|
||||
log.String("k1", "str"),
|
||||
log.Float64("k2", 1.0),
|
||||
log.Int("k3", 2),
|
||||
log.Bool("k4", true),
|
||||
log.Bytes("k5", []byte{1}),
|
||||
log.Slice("k6", log.IntValue(3)),
|
||||
log.Map("k7", log.Bool("sub1", true)),
|
||||
log.String("k8", "str"),
|
||||
log.Float64("k9", 1.0),
|
||||
log.Int("k10", 2),
|
||||
log.Bool("k11", true),
|
||||
log.Bytes("k12", []byte{1}),
|
||||
log.Slice("k13", log.IntValue(3)),
|
||||
log.Map("k14", log.Bool("sub1", true)),
|
||||
{}, // Empty.
|
||||
}
|
||||
|
||||
var r log.Record
|
||||
r.AddAttributes(attrs...)
|
||||
require.Equal(t, len(attrs), r.AttributesLen())
|
||||
|
||||
t.Run("Correctness", func(t *testing.T) {
|
||||
var i int
|
||||
r.WalkAttributes(func(kv log.KeyValue) bool {
|
||||
assert.Equal(t, attrs[i], kv)
|
||||
i++
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("WalkAttributes/Filtering", func(t *testing.T) {
|
||||
for i := 1; i <= len(attrs); i++ {
|
||||
var j int
|
||||
r.WalkAttributes(func(log.KeyValue) bool {
|
||||
j++
|
||||
return j < i
|
||||
})
|
||||
assert.Equal(t, i, j, "number of attributes walked incorrect")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user