mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-01-07 23:02:15 +02:00
467 lines
12 KiB
Go
467 lines
12 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package log // import "go.opentelemetry.io/otel/sdk/log"
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"go.opentelemetry.io/otel/internal/global"
|
|
"go.opentelemetry.io/otel/log"
|
|
"go.opentelemetry.io/otel/sdk/instrumentation"
|
|
"go.opentelemetry.io/otel/sdk/resource"
|
|
"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
|
|
|
|
var logAttrDropped = sync.OnceFunc(func() {
|
|
global.Warn("limit reached: dropping log Record attributes")
|
|
})
|
|
|
|
// indexPool is a pool of index maps used for de-duplication.
|
|
var indexPool = sync.Pool{
|
|
New: func() any { return make(map[string]int) },
|
|
}
|
|
|
|
func getIndex() map[string]int {
|
|
return indexPool.Get().(map[string]int)
|
|
}
|
|
|
|
func putIndex(index map[string]int) {
|
|
clear(index)
|
|
indexPool.Put(index)
|
|
}
|
|
|
|
// Record is a log record emitted by the Logger.
|
|
//
|
|
// Do not create instances of Record on your own in production code.
|
|
// You can use [go.opentelemetry.io/otel/sdk/log/logtest.RecordFactory]
|
|
// for testing purposes.
|
|
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
|
|
|
|
// dropped is the count of attributes that have been dropped when limits
|
|
// were reached.
|
|
dropped int
|
|
|
|
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
|
|
|
|
noCmp [0]func() //nolint: unused // This is indeed used.
|
|
}
|
|
|
|
func (r *Record) addDropped(n int) {
|
|
logAttrDropped()
|
|
r.dropped += n
|
|
}
|
|
|
|
func (r *Record) setDropped(n int) {
|
|
logAttrDropped()
|
|
r.dropped = n
|
|
}
|
|
|
|
// Timestamp returns the time when the log record occurred.
|
|
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) {
|
|
r.timestamp = t
|
|
}
|
|
|
|
// ObservedTimestamp returns the time when the log record was observed.
|
|
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) {
|
|
r.observedTimestamp = t
|
|
}
|
|
|
|
// Severity returns the severity of the log record.
|
|
func (r *Record) Severity() log.Severity {
|
|
return r.severity
|
|
}
|
|
|
|
// SetSeverity sets the severity level of the log record.
|
|
func (r *Record) SetSeverity(level log.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 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) {
|
|
r.severityText = text
|
|
}
|
|
|
|
// Body returns the body of the log record.
|
|
func (r *Record) Body() log.Value {
|
|
return r.body
|
|
}
|
|
|
|
// SetBody sets the body of the log record.
|
|
func (r *Record) SetBody(v log.Value) {
|
|
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) {
|
|
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.
|
|
// Attributes in attrs will overwrite any attribute already added to r with the same key.
|
|
func (r *Record) AddAttributes(attrs ...log.KeyValue) {
|
|
n := r.AttributesLen()
|
|
if n == 0 {
|
|
// Avoid the more complex duplicate map lookups below.
|
|
var drop int
|
|
attrs, drop = dedup(attrs)
|
|
r.setDropped(drop)
|
|
|
|
attrs, drop = head(attrs, r.attributeCountLimit)
|
|
r.addDropped(drop)
|
|
|
|
r.addAttrs(attrs)
|
|
return
|
|
}
|
|
|
|
// Used to find duplicates between attrs and existing attributes in r.
|
|
rIndex := r.attrIndex()
|
|
defer putIndex(rIndex)
|
|
|
|
// Unique attrs that need to be added to r. This uses the same underlying
|
|
// array as attrs.
|
|
//
|
|
// Note, do not iterate attrs twice by just calling dedup(attrs) here.
|
|
unique := attrs[:0]
|
|
// Used to find duplicates within attrs itself. The index value is the
|
|
// index of the element in unique.
|
|
uIndex := getIndex()
|
|
defer putIndex(uIndex)
|
|
|
|
// Deduplicate attrs within the scope of all existing attributes.
|
|
for _, a := range attrs {
|
|
// Last-value-wins for any duplicates in attrs.
|
|
idx, found := uIndex[a.Key]
|
|
if found {
|
|
r.addDropped(1)
|
|
unique[idx] = a
|
|
continue
|
|
}
|
|
|
|
idx, found = rIndex[a.Key]
|
|
if found {
|
|
// New attrs overwrite any existing with the same key.
|
|
r.addDropped(1)
|
|
if idx < 0 {
|
|
r.front[-(idx + 1)] = a
|
|
} else {
|
|
r.back[idx] = a
|
|
}
|
|
} else {
|
|
// Unique attribute.
|
|
unique = append(unique, a)
|
|
uIndex[a.Key] = len(unique) - 1
|
|
}
|
|
}
|
|
attrs = unique
|
|
|
|
if r.attributeCountLimit > 0 && n+len(attrs) > r.attributeCountLimit {
|
|
// Truncate the now unique attributes to comply with limit.
|
|
//
|
|
// Do not use head(attrs, r.attributeCountLimit - n) here. If
|
|
// (r.attributeCountLimit - n) <= 0 attrs needs to be emptied.
|
|
last := max(0, (r.attributeCountLimit - n))
|
|
r.addDropped(len(attrs) - last)
|
|
attrs = attrs[:last]
|
|
}
|
|
|
|
r.addAttrs(attrs)
|
|
}
|
|
|
|
// attrIndex returns an index map for all attributes in the Record r. The index
|
|
// maps the attribute key to location the attribute is stored. If the value is
|
|
// < 0 then -(value + 1) (e.g. -1 -> 0, -2 -> 1, -3 -> 2) represents the index
|
|
// in r.nFront. Otherwise, the index is the exact index of r.back.
|
|
//
|
|
// The returned index is taken from the indexPool. It is the callers
|
|
// responsibility to return the index to that pool (putIndex) when done.
|
|
func (r *Record) attrIndex() map[string]int {
|
|
index := getIndex()
|
|
for i := 0; i < r.nFront; i++ {
|
|
key := r.front[i].Key
|
|
index[key] = -i - 1 // stored in front: negative index.
|
|
}
|
|
for i := 0; i < len(r.back); i++ {
|
|
key := r.back[i].Key
|
|
index[key] = i // stored in back: positive index.
|
|
}
|
|
return index
|
|
}
|
|
|
|
// addAttrs adds attrs to the Record r. This does not validate any limits or
|
|
// duplication of attributes, these tasks are left to the caller to handle
|
|
// prior to calling.
|
|
func (r *Record) addAttrs(attrs []log.KeyValue) {
|
|
var i int
|
|
for i = 0; i < len(attrs) && r.nFront < len(r.front); i++ {
|
|
a := attrs[i]
|
|
r.front[r.nFront] = r.applyAttrLimits(a)
|
|
r.nFront++
|
|
}
|
|
|
|
for j, a := range attrs[i:] {
|
|
attrs[i+j] = r.applyAttrLimits(a)
|
|
}
|
|
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) {
|
|
var drop int
|
|
attrs, drop = dedup(attrs)
|
|
r.setDropped(drop)
|
|
|
|
attrs, drop = head(attrs, r.attributeCountLimit)
|
|
r.addDropped(drop)
|
|
|
|
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] = r.applyAttrLimits(a)
|
|
r.nFront++
|
|
}
|
|
|
|
r.back = slices.Clone(attrs[i:])
|
|
for i, a := range r.back {
|
|
r.back[i] = r.applyAttrLimits(a)
|
|
}
|
|
}
|
|
|
|
// head returns the first n values of kvs along with the number of elements
|
|
// dropped. If n is less than or equal to zero, kvs is returned with 0.
|
|
func head(kvs []log.KeyValue, n int) (out []log.KeyValue, dropped int) {
|
|
if n > 0 && len(kvs) > n {
|
|
return kvs[:n], len(kvs) - n
|
|
}
|
|
return kvs, 0
|
|
}
|
|
|
|
// dedup deduplicates kvs front-to-back with the last value saved.
|
|
func dedup(kvs []log.KeyValue) (unique []log.KeyValue, dropped int) {
|
|
index := getIndex()
|
|
defer putIndex(index)
|
|
|
|
unique = kvs[:0] // Use the same underlying array as kvs.
|
|
for _, a := range kvs {
|
|
idx, found := index[a.Key]
|
|
if found {
|
|
dropped++
|
|
unique[idx] = a
|
|
} else {
|
|
unique = append(unique, a)
|
|
index[a.Key] = len(unique) - 1
|
|
}
|
|
}
|
|
return unique, dropped
|
|
}
|
|
|
|
// AttributesLen returns the number of attributes in the log record.
|
|
func (r *Record) AttributesLen() int {
|
|
return r.nFront + len(r.back)
|
|
}
|
|
|
|
// DroppedAttributes returns the number of attributes dropped due to limits
|
|
// being reached.
|
|
func (r *Record) DroppedAttributes() int {
|
|
return r.dropped
|
|
}
|
|
|
|
// TraceID returns the trace ID or empty array.
|
|
func (r *Record) TraceID() trace.TraceID {
|
|
return r.traceID
|
|
}
|
|
|
|
// SetTraceID sets the trace ID.
|
|
func (r *Record) SetTraceID(id trace.TraceID) {
|
|
r.traceID = id
|
|
}
|
|
|
|
// SpanID returns the span ID or empty array.
|
|
func (r *Record) SpanID() trace.SpanID {
|
|
return r.spanID
|
|
}
|
|
|
|
// SetSpanID sets the span ID.
|
|
func (r *Record) SetSpanID(id trace.SpanID) {
|
|
r.spanID = id
|
|
}
|
|
|
|
// TraceFlags returns the trace flags.
|
|
func (r *Record) TraceFlags() trace.TraceFlags {
|
|
return r.traceFlags
|
|
}
|
|
|
|
// SetTraceFlags sets the trace flags.
|
|
func (r *Record) SetTraceFlags(flags trace.TraceFlags) {
|
|
r.traceFlags = flags
|
|
}
|
|
|
|
// Resource returns the entity that collected the log.
|
|
func (r *Record) Resource() 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 {
|
|
if r.scope == nil {
|
|
return instrumentation.Scope{}
|
|
}
|
|
return *r.scope
|
|
}
|
|
|
|
// 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 {
|
|
res := *r
|
|
res.back = slices.Clone(r.back)
|
|
return res
|
|
}
|
|
|
|
func (r *Record) applyAttrLimits(attr log.KeyValue) log.KeyValue {
|
|
attr.Value = r.applyValueLimits(attr.Value)
|
|
return attr
|
|
}
|
|
|
|
func (r *Record) applyValueLimits(val log.Value) log.Value {
|
|
switch val.Kind() {
|
|
case log.KindString:
|
|
s := val.AsString()
|
|
if len(s) > r.attributeValueLengthLimit {
|
|
val = log.StringValue(truncate(s, r.attributeValueLengthLimit))
|
|
}
|
|
case log.KindSlice:
|
|
sl := val.AsSlice()
|
|
for i := range sl {
|
|
sl[i] = r.applyValueLimits(sl[i])
|
|
}
|
|
val = log.SliceValue(sl...)
|
|
case log.KindMap:
|
|
// Deduplicate then truncate. Do not do at the same time to avoid
|
|
// wasted truncation operations.
|
|
kvs, dropped := dedup(val.AsMap())
|
|
r.addDropped(dropped)
|
|
for i := range kvs {
|
|
kvs[i] = r.applyAttrLimits(kvs[i])
|
|
}
|
|
val = log.MapValue(kvs...)
|
|
}
|
|
return val
|
|
}
|
|
|
|
// truncate returns a copy of str truncated to have a length of at most n
|
|
// characters. If the length of str is less than n, str itself is returned.
|
|
//
|
|
// The truncate of str ensures that no valid UTF-8 code point is split. The
|
|
// copy returned will be less than n if a characters straddles the length
|
|
// limit.
|
|
//
|
|
// No truncation is performed if n is less than zero.
|
|
func truncate(str string, n int) string {
|
|
if n < 0 {
|
|
return str
|
|
}
|
|
|
|
// cut returns a copy of the s truncated to not exceed a length of n. If
|
|
// invalid UTF-8 is encountered, s is returned with false. Otherwise, the
|
|
// truncated copy will be returned with true.
|
|
cut := func(s string) (string, bool) {
|
|
var i int
|
|
for i = 0; i < n; {
|
|
r, size := utf8.DecodeRuneInString(s[i:])
|
|
if r == utf8.RuneError {
|
|
return s, false
|
|
}
|
|
if i+size > n {
|
|
break
|
|
}
|
|
i += size
|
|
}
|
|
return s[:i], true
|
|
}
|
|
|
|
cp, ok := cut(str)
|
|
if !ok {
|
|
cp, _ = cut(strings.ToValidUTF8(str, ""))
|
|
}
|
|
return cp
|
|
}
|