Skip to content

Commit 700227a

Browse files
authored
Incubating: Add minimum severity and trace-based logger configuration (#7529)
1 parent 9a53950 commit 700227a

File tree

9 files changed

+304
-54
lines changed

9 files changed

+304
-54
lines changed

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordBuilder.java

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ final class ExtendedSdkLogRecordBuilder extends SdkLogRecordBuilder
2525
@Nullable private ExtendedAttributesMap extendedAttributes;
2626

2727
ExtendedSdkLogRecordBuilder(
28-
LoggerSharedState loggerSharedState, InstrumentationScopeInfo instrumentationScopeInfo) {
29-
super(loggerSharedState, instrumentationScopeInfo);
28+
LoggerSharedState loggerSharedState,
29+
InstrumentationScopeInfo instrumentationScopeInfo,
30+
SdkLogger logger) {
31+
super(loggerSharedState, instrumentationScopeInfo, logger);
3032
}
3133

3234
@Override
@@ -128,30 +130,18 @@ public <T> ExtendedSdkLogRecordBuilder setAttribute(AttributeKey<T> key, @Nullab
128130
}
129131

130132
@Override
131-
public void emit() {
132-
if (loggerSharedState.hasBeenShutdown()) {
133-
return;
134-
}
135-
Context context = this.context == null ? Context.current() : this.context;
136-
long observedTimestampEpochNanos =
137-
this.observedTimestampEpochNanos == 0
138-
? this.loggerSharedState.getClock().now()
139-
: this.observedTimestampEpochNanos;
140-
loggerSharedState
141-
.getLogRecordProcessor()
142-
.onEmit(
143-
context,
144-
ExtendedSdkReadWriteLogRecord.create(
145-
loggerSharedState.getLogLimits(),
146-
loggerSharedState.getResource(),
147-
instrumentationScopeInfo,
148-
eventName,
149-
timestampEpochNanos,
150-
observedTimestampEpochNanos,
151-
Span.fromContext(context).getSpanContext(),
152-
severity,
153-
severityText,
154-
body,
155-
extendedAttributes));
133+
protected ReadWriteLogRecord createLogRecord(Context context, long observedTimestampEpochNanos) {
134+
return ExtendedSdkReadWriteLogRecord.create(
135+
loggerSharedState.getLogLimits(),
136+
loggerSharedState.getResource(),
137+
instrumentationScopeInfo,
138+
eventName,
139+
timestampEpochNanos,
140+
observedTimestampEpochNanos,
141+
Span.fromContext(context).getSpanContext(),
142+
severity,
143+
severityText,
144+
body,
145+
extendedAttributes);
156146
}
157147
}

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/IncubatingUtil.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ static SdkLogger createExtendedLogger(
2525
}
2626

2727
static SdkLogRecordBuilder createExtendedLogRecordBuilder(
28-
LoggerSharedState loggerSharedState, InstrumentationScopeInfo instrumentationScopeInfo) {
29-
return new ExtendedSdkLogRecordBuilder(loggerSharedState, instrumentationScopeInfo);
28+
LoggerSharedState loggerSharedState,
29+
InstrumentationScopeInfo instrumentationScopeInfo,
30+
SdkLogger logger) {
31+
return new ExtendedSdkLogRecordBuilder(loggerSharedState, instrumentationScopeInfo, logger);
3032
}
3133
}

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class SdkLogRecordBuilder implements LogRecordBuilder {
2222

2323
protected final LoggerSharedState loggerSharedState;
2424
protected final LogLimits logLimits;
25+
protected final SdkLogger logger;
2526

2627
protected final InstrumentationScopeInfo instrumentationScopeInfo;
2728
protected long timestampEpochNanos;
@@ -34,10 +35,13 @@ class SdkLogRecordBuilder implements LogRecordBuilder {
3435
@Nullable private AttributesMap attributes;
3536

3637
SdkLogRecordBuilder(
37-
LoggerSharedState loggerSharedState, InstrumentationScopeInfo instrumentationScopeInfo) {
38+
LoggerSharedState loggerSharedState,
39+
InstrumentationScopeInfo instrumentationScopeInfo,
40+
SdkLogger logger) {
3841
this.loggerSharedState = loggerSharedState;
3942
this.logLimits = loggerSharedState.getLogLimits();
4043
this.instrumentationScopeInfo = instrumentationScopeInfo;
44+
this.logger = logger;
4145
}
4246

4347
@Override
@@ -121,25 +125,30 @@ public void emit() {
121125
return;
122126
}
123127
Context context = this.context == null ? Context.current() : this.context;
128+
if (!logger.isEnabled(severity, context)) {
129+
return;
130+
}
124131
long observedTimestampEpochNanos =
125132
this.observedTimestampEpochNanos == 0
126133
? this.loggerSharedState.getClock().now()
127134
: this.observedTimestampEpochNanos;
128135
loggerSharedState
129136
.getLogRecordProcessor()
130-
.onEmit(
131-
context,
132-
SdkReadWriteLogRecord.create(
133-
loggerSharedState.getLogLimits(),
134-
loggerSharedState.getResource(),
135-
instrumentationScopeInfo,
136-
timestampEpochNanos,
137-
observedTimestampEpochNanos,
138-
Span.fromContext(context).getSpanContext(),
139-
severity,
140-
severityText,
141-
body,
142-
attributes,
143-
eventName));
137+
.onEmit(context, createLogRecord(context, observedTimestampEpochNanos));
138+
}
139+
140+
protected ReadWriteLogRecord createLogRecord(Context context, long observedTimestampEpochNanos) {
141+
return SdkReadWriteLogRecord.create(
142+
loggerSharedState.getLogLimits(),
143+
loggerSharedState.getResource(),
144+
instrumentationScopeInfo,
145+
timestampEpochNanos,
146+
observedTimestampEpochNanos,
147+
Span.fromContext(context).getSpanContext(),
148+
severity,
149+
severityText,
150+
body,
151+
attributes,
152+
eventName);
144153
}
145154
}

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogger.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import io.opentelemetry.api.logs.Logger;
1010
import io.opentelemetry.api.logs.LoggerProvider;
1111
import io.opentelemetry.api.logs.Severity;
12+
import io.opentelemetry.api.trace.Span;
13+
import io.opentelemetry.api.trace.SpanContext;
1214
import io.opentelemetry.context.Context;
1315
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
1416
import io.opentelemetry.sdk.logs.internal.LoggerConfig;
@@ -34,6 +36,8 @@ class SdkLogger implements Logger {
3436
private final InstrumentationScopeInfo instrumentationScopeInfo;
3537

3638
protected volatile boolean loggerEnabled;
39+
protected volatile Severity minimumSeverity;
40+
protected volatile boolean traceBased;
3741

3842
SdkLogger(
3943
LoggerSharedState loggerSharedState,
@@ -42,6 +46,8 @@ class SdkLogger implements Logger {
4246
this.loggerSharedState = loggerSharedState;
4347
this.instrumentationScopeInfo = instrumentationScopeInfo;
4448
this.loggerEnabled = loggerConfig.isEnabled();
49+
this.minimumSeverity = loggerConfig.getMinimumSeverity();
50+
this.traceBased = loggerConfig.isTraceBased();
4551
}
4652

4753
static SdkLogger create(
@@ -58,8 +64,8 @@ public LogRecordBuilder logRecordBuilder() {
5864
if (loggerEnabled) {
5965
return INCUBATOR_AVAILABLE
6066
? IncubatingUtil.createExtendedLogRecordBuilder(
61-
loggerSharedState, instrumentationScopeInfo)
62-
: new SdkLogRecordBuilder(loggerSharedState, instrumentationScopeInfo);
67+
loggerSharedState, instrumentationScopeInfo, this)
68+
: new SdkLogRecordBuilder(loggerSharedState, instrumentationScopeInfo, this);
6369
}
6470
return NOOP_LOGGER.logRecordBuilder();
6571
}
@@ -71,10 +77,28 @@ InstrumentationScopeInfo getInstrumentationScopeInfo() {
7177

7278
// Visible for testing
7379
public boolean isEnabled(Severity severity, Context context) {
74-
return loggerEnabled;
80+
if (!loggerEnabled) {
81+
return false;
82+
}
83+
84+
if (severity != Severity.UNDEFINED_SEVERITY_NUMBER
85+
&& severity.getSeverityNumber() < minimumSeverity.getSeverityNumber()) {
86+
return false;
87+
}
88+
89+
if (traceBased) {
90+
SpanContext spanContext = Span.fromContext(context).getSpanContext();
91+
if (spanContext.isValid() && !spanContext.getTraceFlags().isSampled()) {
92+
return false;
93+
}
94+
}
95+
96+
return true;
7597
}
7698

7799
void updateLoggerConfig(LoggerConfig loggerConfig) {
78100
loggerEnabled = loggerConfig.isEnabled();
101+
minimumSeverity = loggerConfig.getMinimumSeverity();
102+
traceBased = loggerConfig.isTraceBased();
79103
}
80104
}

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/LoggerConfig.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import com.google.auto.value.AutoValue;
99
import io.opentelemetry.api.logs.Logger;
10+
import io.opentelemetry.api.logs.Severity;
1011
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
1112
import io.opentelemetry.sdk.internal.ScopeConfigurator;
1213
import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder;
@@ -30,9 +31,15 @@
3031
public abstract class LoggerConfig {
3132

3233
private static final LoggerConfig DEFAULT_CONFIG =
33-
new AutoValue_LoggerConfig(/* enabled= */ true);
34+
new AutoValue_LoggerConfig(
35+
/* enabled= */ true,
36+
/* minimumSeverity= */ Severity.UNDEFINED_SEVERITY_NUMBER,
37+
/* traceBased= */ false);
3438
private static final LoggerConfig DISABLED_CONFIG =
35-
new AutoValue_LoggerConfig(/* enabled= */ false);
39+
new AutoValue_LoggerConfig(
40+
/* enabled= */ false,
41+
/* minimumSeverity= */ Severity.UNDEFINED_SEVERITY_NUMBER,
42+
/* traceBased= */ false);
3643

3744
/** Returns a disabled {@link LoggerConfig}. */
3845
public static LoggerConfig disabled() {
@@ -44,6 +51,11 @@ public static LoggerConfig enabled() {
4451
return DEFAULT_CONFIG;
4552
}
4653

54+
/** Returns a new {@link LoggerConfigBuilder} for creating a {@link LoggerConfig}. */
55+
public static LoggerConfigBuilder builder() {
56+
return new LoggerConfigBuilder();
57+
}
58+
4759
/**
4860
* Returns the default {@link LoggerConfig}, which is used when no configurator is set or when the
4961
* logger configurator returns {@code null} for a {@link InstrumentationScopeInfo}.
@@ -62,6 +74,31 @@ public static ScopeConfiguratorBuilder<LoggerConfig> configuratorBuilder() {
6274

6375
LoggerConfig() {}
6476

77+
static LoggerConfig create(boolean enabled, Severity minimumSeverity, boolean traceBased) {
78+
return new AutoValue_LoggerConfig(enabled, minimumSeverity, traceBased);
79+
}
80+
6581
/** Returns {@code true} if this logger is enabled. Defaults to {@code true}. */
6682
public abstract boolean isEnabled();
83+
84+
/**
85+
* Returns the minimum severity level for log records to be processed.
86+
*
87+
* <p>Log records with a severity number less than this value will be dropped. Log records without
88+
* a specified severity are not affected by this setting.
89+
*
90+
* <p>Defaults to {@link Severity#UNDEFINED_SEVERITY_NUMBER}.
91+
*/
92+
public abstract Severity getMinimumSeverity();
93+
94+
/**
95+
* Returns {@code true} if this logger should only process log records from traces when the trace
96+
* is sampled.
97+
*
98+
* <p>When enabled, log records from unsampled traces will be dropped. Log records that are not
99+
* associated with a trace context are unaffected.
100+
*
101+
* <p>Defaults to {@code false}.
102+
*/
103+
public abstract boolean isTraceBased();
67104
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.sdk.logs.internal;
7+
8+
import io.opentelemetry.api.logs.Severity;
9+
10+
/**
11+
* Builder for {@link LoggerConfig}.
12+
*
13+
* <p>This class is internal and experimental. Its APIs are unstable and can change at any time. Its
14+
* APIs (or a version of them) may be promoted to the public stable API in the future, but no
15+
* guarantees are made.
16+
*/
17+
public final class LoggerConfigBuilder {
18+
private boolean enabled = true;
19+
private Severity minimumSeverity = Severity.UNDEFINED_SEVERITY_NUMBER;
20+
private boolean traceBased = false;
21+
22+
LoggerConfigBuilder() {}
23+
24+
/**
25+
* Sets whether the logger is enabled.
26+
*
27+
* @param enabled whether the logger is enabled
28+
* @return this builder
29+
*/
30+
public LoggerConfigBuilder setEnabled(boolean enabled) {
31+
this.enabled = enabled;
32+
return this;
33+
}
34+
35+
/**
36+
* Sets the minimum severity level for log records to be processed.
37+
*
38+
* <p>Log records with a severity number less than this value will be dropped. Log records without
39+
* a specified severity are not affected by this setting.
40+
*
41+
* @param minimumSeverity minimum severity level for log records to be processed
42+
* @return this builder
43+
*/
44+
public LoggerConfigBuilder setMinimumSeverity(Severity minimumSeverity) {
45+
this.minimumSeverity = minimumSeverity;
46+
return this;
47+
}
48+
49+
/**
50+
* Sets whether to only process log records from traces when the trace is sampled.
51+
*
52+
* <p>When enabled, log records from unsampled traces will be dropped. Log records that are not
53+
* associated with a trace context are unaffected.
54+
*
55+
* @param traceBased whether to only process log records from traces when the trace is sampled
56+
* @return this builder
57+
*/
58+
public LoggerConfigBuilder setTraceBased(boolean traceBased) {
59+
this.traceBased = traceBased;
60+
return this;
61+
}
62+
63+
/** Builds and returns a {@link LoggerConfig}. */
64+
public LoggerConfig build() {
65+
return LoggerConfig.create(enabled, minimumSeverity, traceBased);
66+
}
67+
}

sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilderTest.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
import static io.opentelemetry.api.common.AttributeKey.stringKey;
1212
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
1313
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
14+
import static org.mockito.ArgumentMatchers.eq;
15+
import static org.mockito.Mockito.mock;
16+
import static org.mockito.Mockito.verify;
1417
import static org.mockito.Mockito.when;
1518

1619
import io.opentelemetry.api.common.AttributeKey;
@@ -24,6 +27,7 @@
2427
import io.opentelemetry.context.Context;
2528
import io.opentelemetry.sdk.common.Clock;
2629
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
30+
import io.opentelemetry.sdk.logs.internal.LoggerConfig;
2731
import io.opentelemetry.sdk.resources.Resource;
2832
import java.time.Instant;
2933
import java.util.concurrent.TimeUnit;
@@ -57,7 +61,8 @@ void setup() {
5761
when(loggerSharedState.getResource()).thenReturn(RESOURCE);
5862
when(loggerSharedState.getClock()).thenReturn(clock);
5963

60-
builder = new SdkLogRecordBuilder(loggerSharedState, SCOPE_INFO);
64+
SdkLogger logger = new SdkLogger(loggerSharedState, SCOPE_INFO, LoggerConfig.enabled());
65+
builder = new SdkLogRecordBuilder(loggerSharedState, SCOPE_INFO, logger);
6166
}
6267

6368
@Test
@@ -121,6 +126,26 @@ void emit_NoFields() {
121126
.hasSeverity(Severity.UNDEFINED_SEVERITY_NUMBER);
122127
}
123128

129+
@Test
130+
void emit_ChecksLoggerIsEnabled() {
131+
SdkLogger logger = mock(SdkLogger.class);
132+
builder = new SdkLogRecordBuilder(loggerSharedState, SCOPE_INFO, logger);
133+
134+
Severity severity = Severity.WARN;
135+
Context context = Context.current();
136+
137+
// Configure mock to return false (disabled)
138+
when(logger.isEnabled(severity, context)).thenReturn(false);
139+
140+
builder.setSeverity(severity).setContext(context).emit();
141+
142+
// Verify isEnabled was called with correct parameters
143+
verify(logger).isEnabled(eq(severity), eq(context));
144+
145+
// Verify log was not processed (because isEnabled returned false)
146+
assertThat(emittedLog.get()).isNull();
147+
}
148+
124149
@Test
125150
void testConvenienceAttributeMethods() {
126151
builder

0 commit comments

Comments
 (0)