Skip to content

Commit 5da6cd2

Browse files
authored
sdk/log: Add WithAllowKeyDuplication logger provider option (#6968)
Closes #5133 This couldn't be added as an option on a processor, as that would involve moving all the attribute deduplication. logic outside of the record type. Instead this PR provides the same functionality but it is set when creating the log provider The below benchstat report shows the performance improvement when `allowDupKeys` is set ``` goos: darwin goarch: arm64 pkg: go.opentelemetry.io/otel/sdk/log cpu: Apple M2 Pro │ withoutDedup.txt │ withDedup.txt │ │ sec/op │ sec/op vs base │ SetAddAttributes/SetAttributes-12 141.3n ± 2% 167.4n ± 5% +18.51% (p=0.000 n=10) SetAddAttributes/AddAttributes-12 117.5n ± 2% 124.8n ± 5% +6.17% (p=0.000 n=10) geomean 128.9n 144.5n +12.17% │ withoutDedup.txt │ withDedup.txt │ │ B/op │ B/op vs base │ SetAddAttributes/SetAttributes-12 48.00 ± 0% 48.00 ± 0% ~ (p=1.000 n=10) ¹ SetAddAttributes/AddAttributes-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean │ withoutDedup.txt │ withDedup.txt │ │ allocs/op │ allocs/op vs base │ SetAddAttributes/SetAttributes-12 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ SetAddAttributes/AddAttributes-12 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean ```
1 parent e4c84b9 commit 5da6cd2

File tree

7 files changed

+299
-43
lines changed

7 files changed

+299
-43
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
4040
- `RPCGRPCRequestMetadata`
4141
- `RPCGRPCResponseMetadata`
4242
- Add `ErrorType` attribute helper function to the `go.opentelmetry.io/otel/semconv/v1.34.0` package. (#6962)
43+
- Add `WithAllowKeyDuplication` in `go.opentelemetry.io/otel/sdk/log` which can be used to disable deduplication for log records. (#6968)
4344

4445
### Changed
4546

sdk/log/logger.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ func (l *logger) newRecord(ctx context.Context, r log.Record) Record {
9494
scope: &l.instrumentationScope,
9595
attributeValueLengthLimit: l.provider.attributeValueLengthLimit,
9696
attributeCountLimit: l.provider.attributeCountLimit,
97+
allowDupKeys: l.provider.allowDupKeys,
9798
}
9899

99100
// This field SHOULD be set once the event is observed by OpenTelemetry.

sdk/log/logger_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ func TestLoggerEmit(t *testing.T) {
4747
rWithNoObservedTimestamp := r
4848
rWithNoObservedTimestamp.SetObservedTimestamp(time.Time{})
4949

50+
rWithoutDeduplicateAttributes := r
51+
rWithoutDeduplicateAttributes.AddAttributes(
52+
log.String("k1", "str1"),
53+
)
54+
5055
contextWithSpanContext := trace.ContextWithSpanContext(
5156
context.Background(),
5257
trace.NewSpanContext(trace.SpanContextConfig{
@@ -206,6 +211,40 @@ func TestLoggerEmit(t *testing.T) {
206211
},
207212
},
208213
},
214+
{
215+
name: "WithoutAttributeDeduplication",
216+
logger: newLogger(NewLoggerProvider(
217+
WithProcessor(p0),
218+
WithProcessor(p1),
219+
WithAttributeValueLengthLimit(5),
220+
WithAttributeCountLimit(5),
221+
WithResource(resource.NewSchemaless(attribute.String("key", "value"))),
222+
WithAllowKeyDuplication(),
223+
), instrumentation.Scope{Name: "scope"}),
224+
ctx: context.Background(),
225+
record: rWithoutDeduplicateAttributes,
226+
expectedRecords: []Record{
227+
{
228+
eventName: r.EventName(),
229+
timestamp: r.Timestamp(),
230+
body: r.Body(),
231+
severity: r.Severity(),
232+
severityText: r.SeverityText(),
233+
observedTimestamp: r.ObservedTimestamp(),
234+
resource: resource.NewSchemaless(attribute.String("key", "value")),
235+
attributeValueLengthLimit: 5,
236+
attributeCountLimit: 5,
237+
scope: &instrumentation.Scope{Name: "scope"},
238+
front: [attributesInlineCount]log.KeyValue{
239+
log.String("k1", "str"),
240+
log.Float64("k2", 1.0),
241+
log.String("k1", "str1"),
242+
},
243+
nFront: 3,
244+
allowDupKeys: true,
245+
},
246+
},
247+
},
209248
}
210249

211250
for _, tc := range testCases {

sdk/log/provider.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type providerConfig struct {
3232
fltrProcessors []FilterProcessor
3333
attrCntLim setting[int]
3434
attrValLenLim setting[int]
35+
allowDupKeys setting[bool]
3536
}
3637

3738
func newProviderConfig(opts []LoggerProviderOption) providerConfig {
@@ -67,6 +68,7 @@ type LoggerProvider struct {
6768
fltrProcessors []FilterProcessor
6869
attributeCountLimit int
6970
attributeValueLengthLimit int
71+
allowDupKeys bool
7072

7173
loggersMu sync.Mutex
7274
loggers map[instrumentation.Scope]*logger
@@ -93,6 +95,7 @@ func NewLoggerProvider(opts ...LoggerProviderOption) *LoggerProvider {
9395
fltrProcessors: cfg.fltrProcessors,
9496
attributeCountLimit: cfg.attrCntLim.Value,
9597
attributeValueLengthLimit: cfg.attrValLenLim.Value,
98+
allowDupKeys: cfg.allowDupKeys.Value,
9699
}
97100
}
98101

@@ -254,3 +257,21 @@ func WithAttributeValueLengthLimit(limit int) LoggerProviderOption {
254257
return cfg
255258
})
256259
}
260+
261+
// WithAllowKeyDuplication sets whether deduplication is skipped for log attributes or other key-value collections.
262+
//
263+
// By default, the key-value collections within a log record are deduplicated to comply with the OpenTelemetry Specification.
264+
// Deduplication means that if multiple key–value pairs with the same key are present, only a single pair
265+
// is retained and others are discarded.
266+
//
267+
// Disabling deduplication with this option can improve performance e.g. of adding attributes to the log record.
268+
//
269+
// Note that if you disable deduplication, you are responsible for ensuring that duplicate
270+
// key-value pairs within in a single collection are not emitted,
271+
// or that the telemetry receiver can handle such duplicates.
272+
func WithAllowKeyDuplication() LoggerProviderOption {
273+
return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig {
274+
cfg.allowDupKeys = newSetting(true)
275+
return cfg
276+
})
277+
}

sdk/log/provider_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,14 @@ func TestNewLoggerProviderConfiguration(t *testing.T) {
115115
WithProcessor(p1),
116116
WithAttributeCountLimit(attrCntLim),
117117
WithAttributeValueLengthLimit(attrValLenLim),
118+
WithAllowKeyDuplication(),
118119
},
119120
want: &LoggerProvider{
120121
resource: res,
121122
processors: []Processor{p0, p1},
122123
attributeCountLimit: attrCntLim,
123124
attributeValueLengthLimit: attrValLenLim,
125+
allowDupKeys: true,
124126
},
125127
},
126128
{

sdk/log/record.go

Lines changed: 57 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ type Record struct {
9393
attributeValueLengthLimit int
9494
attributeCountLimit int
9595

96+
// specifies whether we should deduplicate any key value collections or not
97+
allowDupKeys bool
98+
9699
noCmp [0]func() //nolint: unused // This is indeed used.
97100
}
98101

@@ -192,56 +195,60 @@ func (r *Record) AddAttributes(attrs ...log.KeyValue) {
192195
if n == 0 {
193196
// Avoid the more complex duplicate map lookups below.
194197
var drop int
195-
attrs, drop = dedup(attrs)
196-
r.setDropped(drop)
198+
if !r.allowDupKeys {
199+
attrs, drop = dedup(attrs)
200+
r.setDropped(drop)
201+
}
197202

198-
attrs, drop = head(attrs, r.attributeCountLimit)
203+
attrs, drop := head(attrs, r.attributeCountLimit)
199204
r.addDropped(drop)
200205

201206
r.addAttrs(attrs)
202207
return
203208
}
204209

205-
// Used to find duplicates between attrs and existing attributes in r.
206-
rIndex := r.attrIndex()
207-
defer putIndex(rIndex)
210+
if !r.allowDupKeys {
211+
// Used to find duplicates between attrs and existing attributes in r.
212+
rIndex := r.attrIndex()
213+
defer putIndex(rIndex)
208214

209-
// Unique attrs that need to be added to r. This uses the same underlying
210-
// array as attrs.
211-
//
212-
// Note, do not iterate attrs twice by just calling dedup(attrs) here.
213-
unique := attrs[:0]
214-
// Used to find duplicates within attrs itself. The index value is the
215-
// index of the element in unique.
216-
uIndex := getIndex()
217-
defer putIndex(uIndex)
218-
219-
// Deduplicate attrs within the scope of all existing attributes.
220-
for _, a := range attrs {
221-
// Last-value-wins for any duplicates in attrs.
222-
idx, found := uIndex[a.Key]
223-
if found {
224-
r.addDropped(1)
225-
unique[idx] = a
226-
continue
227-
}
215+
// Unique attrs that need to be added to r. This uses the same underlying
216+
// array as attrs.
217+
//
218+
// Note, do not iterate attrs twice by just calling dedup(attrs) here.
219+
unique := attrs[:0]
220+
// Used to find duplicates within attrs itself. The index value is the
221+
// index of the element in unique.
222+
uIndex := getIndex()
223+
defer putIndex(uIndex)
224+
225+
// Deduplicate attrs within the scope of all existing attributes.
226+
for _, a := range attrs {
227+
// Last-value-wins for any duplicates in attrs.
228+
idx, found := uIndex[a.Key]
229+
if found {
230+
r.addDropped(1)
231+
unique[idx] = a
232+
continue
233+
}
228234

229-
idx, found = rIndex[a.Key]
230-
if found {
231-
// New attrs overwrite any existing with the same key.
232-
r.addDropped(1)
233-
if idx < 0 {
234-
r.front[-(idx + 1)] = a
235+
idx, found = rIndex[a.Key]
236+
if found {
237+
// New attrs overwrite any existing with the same key.
238+
r.addDropped(1)
239+
if idx < 0 {
240+
r.front[-(idx + 1)] = a
241+
} else {
242+
r.back[idx] = a
243+
}
235244
} else {
236-
r.back[idx] = a
245+
// Unique attribute.
246+
unique = append(unique, a)
247+
uIndex[a.Key] = len(unique) - 1
237248
}
238-
} else {
239-
// Unique attribute.
240-
unique = append(unique, a)
241-
uIndex[a.Key] = len(unique) - 1
242249
}
250+
attrs = unique
243251
}
244-
attrs = unique
245252

246253
if r.attributeCountLimit > 0 && n+len(attrs) > r.attributeCountLimit {
247254
// Truncate the now unique attributes to comply with limit.
@@ -297,8 +304,11 @@ func (r *Record) addAttrs(attrs []log.KeyValue) {
297304
// SetAttributes sets (and overrides) attributes to the log record.
298305
func (r *Record) SetAttributes(attrs ...log.KeyValue) {
299306
var drop int
300-
attrs, drop = dedup(attrs)
301-
r.setDropped(drop)
307+
r.setDropped(0)
308+
if !r.allowDupKeys {
309+
attrs, drop = dedup(attrs)
310+
r.setDropped(drop)
311+
}
302312

303313
attrs, drop = head(attrs, r.attributeCountLimit)
304314
r.addDropped(drop)
@@ -426,10 +436,14 @@ func (r *Record) applyValueLimits(val log.Value) log.Value {
426436
}
427437
val = log.SliceValue(sl...)
428438
case log.KindMap:
429-
// Deduplicate then truncate. Do not do at the same time to avoid
430-
// wasted truncation operations.
431-
kvs, dropped := dedup(val.AsMap())
432-
r.addDropped(dropped)
439+
kvs := val.AsMap()
440+
if !r.allowDupKeys {
441+
// Deduplicate then truncate. Do not do at the same time to avoid
442+
// wasted truncation operations.
443+
var dropped int
444+
kvs, dropped = dedup(kvs)
445+
r.addDropped(dropped)
446+
}
433447
for i := range kvs {
434448
kvs[i] = r.applyAttrLimits(kvs[i])
435449
}

0 commit comments

Comments
 (0)