Skip to content

Commit 9cb7c2c

Browse files
committed
Added setLoggerProvider method to appender to allow for SDK based implementations to use it (#8222)
1 parent 506ca93 commit 9cb7c2c

File tree

2 files changed

+279
-3
lines changed

2 files changed

+279
-3
lines changed

instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppender.java

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.google.errorprone.annotations.CanIgnoreReturnValue;
1111
import io.opentelemetry.api.logs.GlobalLoggerProvider;
1212
import io.opentelemetry.api.logs.LogRecordBuilder;
13+
import io.opentelemetry.api.logs.LoggerProvider;
1314
import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.ContextDataAccessor;
1415
import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.LogEventMapper;
1516
import java.io.Serializable;
@@ -43,6 +44,7 @@ public class OpenTelemetryAppender extends AbstractAppender {
4344
static final String PLUGIN_NAME = "OpenTelemetry";
4445

4546
private final LogEventMapper<ReadOnlyStringMap> mapper;
47+
private LoggerProvider loggerProvider;
4648

4749
@PluginBuilderFactory
4850
public static <B extends Builder<B>> B builder() {
@@ -57,6 +59,8 @@ public static class Builder<B extends Builder<B>> extends AbstractAppender.Build
5759
@PluginBuilderAttribute private boolean captureMarkerAttribute;
5860
@PluginBuilderAttribute private String captureContextDataAttributes;
5961

62+
private LoggerProvider loggerProvider;
63+
6064
/**
6165
* Sets whether experimental attributes should be set to logs. These attributes may be changed
6266
* or removed in the future, so only enable this if you know you do not require attributes
@@ -93,6 +97,12 @@ public B setCaptureContextDataAttributes(String captureContextDataAttributes) {
9397
return asBuilder();
9498
}
9599

100+
@CanIgnoreReturnValue
101+
public B setLoggerProvider(LoggerProvider loggerProvider) {
102+
this.loggerProvider = loggerProvider;
103+
return asBuilder();
104+
}
105+
96106
@Override
97107
public OpenTelemetryAppender build() {
98108
return new OpenTelemetryAppender(
@@ -104,7 +114,8 @@ public OpenTelemetryAppender build() {
104114
captureExperimentalAttributes,
105115
captureMapMessageAttributes,
106116
captureMarkerAttribute,
107-
captureContextDataAttributes);
117+
captureContextDataAttributes,
118+
loggerProvider);
108119
}
109120
}
110121

@@ -117,7 +128,8 @@ private OpenTelemetryAppender(
117128
boolean captureExperimentalAttributes,
118129
boolean captureMapMessageAttributes,
119130
boolean captureMarkerAttribute,
120-
String captureContextDataAttributes) {
131+
String captureContextDataAttributes,
132+
LoggerProvider loggerProvider) {
121133

122134
super(name, filter, layout, ignoreExceptions, properties);
123135
this.mapper =
@@ -127,6 +139,7 @@ private OpenTelemetryAppender(
127139
captureMapMessageAttributes,
128140
captureMarkerAttribute,
129141
splitAndFilterBlanksAndNulls(captureContextDataAttributes));
142+
this.loggerProvider = loggerProvider;
130143
}
131144

132145
private static List<String> splitAndFilterBlanksAndNulls(String value) {
@@ -139,14 +152,24 @@ private static List<String> splitAndFilterBlanksAndNulls(String value) {
139152
.collect(Collectors.toList());
140153
}
141154

155+
public void setLoggerProvider(LoggerProvider loggerProvider) {
156+
this.loggerProvider = loggerProvider;
157+
}
158+
159+
public LoggerProvider getLoggerProvider() {
160+
return loggerProvider == null ? GlobalLoggerProvider.get() : loggerProvider;
161+
}
162+
142163
@Override
143164
public void append(LogEvent event) {
144165
String instrumentationName = event.getLoggerName();
145166
if (instrumentationName == null || instrumentationName.isEmpty()) {
146167
instrumentationName = "ROOT";
147168
}
169+
148170
LogRecordBuilder builder =
149-
GlobalLoggerProvider.get().loggerBuilder(instrumentationName).build().logRecordBuilder();
171+
getLoggerProvider()
172+
.loggerBuilder(instrumentationName).build().logRecordBuilder();
150173
ReadOnlyStringMap contextData = event.getContextData();
151174
mapper.mapLogEvent(
152175
builder,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.log4j.appender.v2_17;
7+
8+
import static io.opentelemetry.api.common.AttributeKey.stringKey;
9+
import static io.opentelemetry.sdk.testing.assertj.LogAssertions.assertThat;
10+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
11+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
12+
13+
import io.opentelemetry.api.common.Attributes;
14+
import io.opentelemetry.api.logs.LoggerProvider;
15+
import io.opentelemetry.api.logs.Severity;
16+
import io.opentelemetry.api.trace.Span;
17+
import io.opentelemetry.api.trace.SpanContext;
18+
import io.opentelemetry.context.Scope;
19+
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
20+
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
21+
import io.opentelemetry.sdk.logs.data.LogRecordData;
22+
import io.opentelemetry.sdk.logs.export.InMemoryLogRecordExporter;
23+
import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;
24+
import io.opentelemetry.sdk.resources.Resource;
25+
import io.opentelemetry.sdk.trace.SdkTracerProvider;
26+
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
27+
import java.time.Instant;
28+
import java.util.List;
29+
import java.util.concurrent.TimeUnit;
30+
import org.apache.logging.log4j.LogManager;
31+
import org.apache.logging.log4j.Logger;
32+
import org.apache.logging.log4j.Marker;
33+
import org.apache.logging.log4j.MarkerManager;
34+
import org.apache.logging.log4j.ThreadContext;
35+
import org.apache.logging.log4j.core.LoggerContext;
36+
import org.apache.logging.log4j.core.config.Configuration;
37+
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
38+
import org.apache.logging.log4j.message.FormattedMessage;
39+
import org.apache.logging.log4j.message.StringMapMessage;
40+
import org.apache.logging.log4j.message.StructuredDataMessage;
41+
import org.junit.jupiter.api.AfterAll;
42+
import org.junit.jupiter.api.BeforeAll;
43+
import org.junit.jupiter.api.BeforeEach;
44+
import org.junit.jupiter.api.Test;
45+
46+
class OpenTelemetryAppenderConfigLoggerProviderTest {
47+
48+
private static final Logger logger = LogManager.getLogger("TestLogger");
49+
50+
private static InMemoryLogRecordExporter logRecordExporter;
51+
private static Resource resource;
52+
private static InstrumentationScopeInfo instrumentationScopeInfo;
53+
private static LoggerProvider loggerProvider;
54+
55+
@BeforeAll
56+
static void setupAll() {
57+
logRecordExporter = InMemoryLogRecordExporter.create();
58+
resource = Resource.getDefault();
59+
instrumentationScopeInfo = InstrumentationScopeInfo.create("TestLogger");
60+
61+
loggerProvider =
62+
SdkLoggerProvider.builder()
63+
.setResource(resource)
64+
.addLogRecordProcessor(SimpleLogRecordProcessor.create(logRecordExporter))
65+
.build();
66+
67+
setLoggerProvider(loggerProvider);
68+
}
69+
70+
private static void setLoggerProvider(LoggerProvider loggerProvider) {
71+
Configuration config = ((LoggerContext) LogManager.getContext(false)).getConfiguration();
72+
config.getAppenders().values().stream()
73+
.filter(a -> a instanceof OpenTelemetryAppender)
74+
.forEach(a -> ((OpenTelemetryAppender) a).setLoggerProvider(loggerProvider));
75+
}
76+
77+
@AfterAll
78+
static void cleanupAll() {
79+
//This is to make sure that other test classes don't have issues with the logger provider set
80+
setLoggerProvider(null);
81+
}
82+
83+
@BeforeEach
84+
void setup() {
85+
logRecordExporter.reset();
86+
ThreadContext.clearAll();
87+
}
88+
89+
@Test
90+
void initializeWithBuilder() {
91+
OpenTelemetryAppender appender =
92+
OpenTelemetryAppender.builder()
93+
.setName("OpenTelemetryAppender")
94+
.setLoggerProvider(loggerProvider)
95+
.build();
96+
appender.start();
97+
98+
appender.append(
99+
Log4jLogEvent.newBuilder()
100+
.setMessage(new FormattedMessage("log message 1", (Object) null))
101+
.build());
102+
103+
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
104+
assertThat(logDataList)
105+
.satisfiesExactly(logRecordData -> assertThat(logDataList.get(0)).hasBody("log message 1"));
106+
}
107+
108+
109+
@Test
110+
void logNoSpan() {
111+
logger.info("logNoSpan - log message 1");
112+
113+
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
114+
assertThat(logDataList).hasSize(1);
115+
assertThat(logDataList.get(0))
116+
.hasResource(resource)
117+
.hasInstrumentationScope(instrumentationScopeInfo)
118+
.hasBody("logNoSpan - log message 1")
119+
.hasAttributes(Attributes.empty());
120+
}
121+
122+
@Test
123+
void logWithSpan() {
124+
Span span1 = runWithSpan("span1", () -> logger.info("logWithSpan - log message 1"));
125+
126+
logger.info("logWithSpan - log message 2");
127+
128+
Span span2 = runWithSpan("span2", () -> logger.info("logWithSpan - log message 3"));
129+
130+
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
131+
assertThat(logDataList).hasSize(3);
132+
assertThat(logDataList.get(0).getSpanContext()).isEqualTo(span1.getSpanContext());
133+
assertThat(logDataList.get(1).getSpanContext()).isEqualTo(SpanContext.getInvalid());
134+
assertThat(logDataList.get(2).getSpanContext()).isEqualTo(span2.getSpanContext());
135+
}
136+
137+
private static Span runWithSpan(String spanName, Runnable runnable) {
138+
Span span = SdkTracerProvider.builder().build().get("tracer").spanBuilder(spanName).startSpan();
139+
try (Scope ignored = span.makeCurrent()) {
140+
runnable.run();
141+
} finally {
142+
span.end();
143+
}
144+
return span;
145+
}
146+
147+
@Test
148+
void logWithExtras() {
149+
Instant start = Instant.now();
150+
logger.info("logWithExtras - log message 1", new IllegalStateException("Error!"));
151+
152+
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
153+
assertThat(logDataList).hasSize(1);
154+
assertThat(logDataList.get(0))
155+
.hasResource(resource)
156+
.hasInstrumentationScope(instrumentationScopeInfo)
157+
.hasBody("logWithExtras - log message 1")
158+
.hasSeverity(Severity.INFO)
159+
.hasSeverityText("INFO")
160+
.hasAttributesSatisfyingExactly(
161+
equalTo(SemanticAttributes.EXCEPTION_TYPE, IllegalStateException.class.getName()),
162+
equalTo(SemanticAttributes.EXCEPTION_MESSAGE, "Error!"),
163+
satisfies(SemanticAttributes.EXCEPTION_STACKTRACE, v -> v.contains("logWithExtras")));
164+
165+
assertThat(logDataList.get(0).getEpochNanos())
166+
.isGreaterThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(start.toEpochMilli()))
167+
.isLessThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(Instant.now().toEpochMilli()));
168+
}
169+
170+
@Test
171+
void logContextData() {
172+
ThreadContext.put("key1", "val1");
173+
ThreadContext.put("key2", "val2");
174+
try {
175+
logger.info("logContextData - log message 1");
176+
} finally {
177+
ThreadContext.clearMap();
178+
}
179+
180+
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
181+
assertThat(logDataList).hasSize(1);
182+
assertThat(logDataList.get(0))
183+
.hasResource(resource)
184+
.hasInstrumentationScope(instrumentationScopeInfo)
185+
.hasBody("logContextData - log message 1")
186+
.hasAttributesSatisfyingExactly(
187+
equalTo(stringKey("log4j.context_data.key1"), "val1"),
188+
equalTo(stringKey("log4j.context_data.key2"), "val2"));
189+
}
190+
191+
@Test
192+
void logStringMapMessage() {
193+
StringMapMessage message = new StringMapMessage();
194+
message.put("key1", "val1");
195+
message.put("key2", "logStringMapMessage - val2");
196+
logger.info(message);
197+
198+
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
199+
assertThat(logDataList).hasSize(1);
200+
assertThat(logDataList.get(0))
201+
.hasResource(resource)
202+
.hasInstrumentationScope(instrumentationScopeInfo)
203+
.hasAttributesSatisfyingExactly(
204+
equalTo(stringKey("log4j.map_message.key1"), "val1"),
205+
equalTo(stringKey("log4j.map_message.key2"), "logStringMapMessage - val2"));
206+
}
207+
208+
@Test
209+
void logStringMapMessageWithSpecialAttribute() {
210+
StringMapMessage message = new StringMapMessage();
211+
message.put("key1", "val1");
212+
message.put("message", "logStringMapMessageWithSpecialAttribute - val2");
213+
logger.info(message);
214+
215+
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
216+
assertThat(logDataList).hasSize(1);
217+
assertThat(logDataList.get(0))
218+
.hasResource(resource)
219+
.hasInstrumentationScope(instrumentationScopeInfo)
220+
.hasBody("logStringMapMessageWithSpecialAttribute - val2")
221+
.hasAttributesSatisfyingExactly(equalTo(stringKey("log4j.map_message.key1"), "val1"));
222+
}
223+
224+
@Test
225+
void testCaptureMarkerAttribute() {
226+
String markerName = "aMarker";
227+
Marker marker = MarkerManager.getMarker(markerName);
228+
229+
logger.info(marker, "testCaptureMarkerAttribute - Message");
230+
231+
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
232+
LogRecordData logData = logDataList.get(0);
233+
assertThat(logData.getAttributes().get(stringKey("log4j.marker"))).isEqualTo(markerName);
234+
}
235+
236+
@Test
237+
void logStructuredDataMessage() {
238+
StructuredDataMessage message = new StructuredDataMessage("an id", "logStructuredDataMessage - a message", "a type");
239+
message.put("key1", "val1");
240+
message.put("key2", "val2");
241+
logger.info(message);
242+
243+
List<LogRecordData> logDataList = logRecordExporter.getFinishedLogItems();
244+
assertThat(logDataList).hasSize(1);
245+
assertThat(logDataList.get(0))
246+
.hasResource(resource)
247+
.hasInstrumentationScope(instrumentationScopeInfo)
248+
.hasBody("logStructuredDataMessage - a message")
249+
.hasAttributesSatisfyingExactly(
250+
equalTo(stringKey("log4j.map_message.key1"), "val1"),
251+
equalTo(stringKey("log4j.map_message.key2"), "val2"));
252+
}
253+
}

0 commit comments

Comments
 (0)