Skip to content

Commit c5cb948

Browse files
jeanbisuttitrask
andauthored
Ability to instrument logs before OTel injection into OTel appenders (#9798)
Co-authored-by: Trask Stalnaker <[email protected]>
1 parent f491250 commit c5cb948

File tree

19 files changed

+1184
-427
lines changed

19 files changed

+1184
-427
lines changed

instrumentation/log4j/log4j-appender-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/appender/v2_17/Log4jHelper.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ public final class Log4jHelper {
2727

2828
private static final LogEventMapper<Map<String, String>> mapper;
2929

30+
private static final boolean captureExperimentalAttributes;
31+
3032
static {
3133
InstrumentationConfig config = InstrumentationConfig.get();
3234

33-
boolean captureExperimentalAttributes =
35+
captureExperimentalAttributes =
3436
config.getBoolean("otel.instrumentation.log4j-appender.experimental-log-attributes", false);
3537
boolean captureMapMessageAttributes =
3638
config.getBoolean(
@@ -66,7 +68,16 @@ public static void capture(
6668
.build()
6769
.logRecordBuilder();
6870
Map<String, String> contextData = ThreadContext.getImmutableContext();
69-
mapper.mapLogEvent(builder, message, level, marker, throwable, contextData);
71+
72+
String threadName = null;
73+
long threadId = -1;
74+
if (captureExperimentalAttributes) {
75+
Thread currentThread = Thread.currentThread();
76+
threadName = currentThread.getName();
77+
threadId = currentThread.getId();
78+
}
79+
mapper.mapLogEvent(
80+
builder, message, level, marker, throwable, contextData, threadName, threadId);
7081
builder.setTimestamp(Instant.now());
7182
builder.emit();
7283
}

instrumentation/log4j/log4j-appender-2.17/library/README.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,12 @@ Setting can be configured as XML attributes, for example:
9393

9494
The available settings are:
9595

96-
| XML Attribute | Type | Default | Description |
97-
| ------------------------------- | ------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
98-
| `captureExperimentalAttributes` | Boolean | `false` | Enable the capture of experimental span attributes `thread.name` and `thread.id`. |
99-
| `captureMapMessageAttributes` | Boolean | `false` | Enable the capture of `MapMessage` attributes. |
100-
| `captureMarkerAttribute;` | Boolean | `false` | Enable the capture of Log4j markers as attributes. |
101-
| `captureContextDataAttributes` | String | | Comma separated list of context data attributes to capture. Use the wildcard character `*` to capture all attributes. |
96+
| XML Attribute | Type | Default | Description |
97+
|---------------------------------|---------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
98+
| `captureExperimentalAttributes` | Boolean | `false` | Enable the capture of experimental span attributes `thread.name` and `thread.id`. |
99+
| `captureMapMessageAttributes` | Boolean | `false` | Enable the capture of `MapMessage` attributes. |
100+
| `captureMarkerAttribute;` | Boolean | `false` | Enable the capture of Log4j markers as attributes. |
101+
| `captureContextDataAttributes` | String | | Comma separated list of context data attributes to capture. Use the wildcard character `*` to capture all attributes. |
102+
| `numLogsCapturedBeforeOtelInstall` | Integer | 1000 | Log telemetry is emitted after the initialization of the OpenTelemetry Log4j appender with an OpenTelemetry object. This setting allows you to modify the size of the cache used to replay the first logs. |
102103

103104
[source code attributes]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/general/attributes.md#source-code-attributes
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
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 java.util.Collections;
9+
import java.util.Map;
10+
import javax.annotation.Nullable;
11+
import org.apache.logging.log4j.Level;
12+
import org.apache.logging.log4j.Marker;
13+
import org.apache.logging.log4j.ThreadContext;
14+
import org.apache.logging.log4j.core.LogEvent;
15+
import org.apache.logging.log4j.core.impl.ThrowableProxy;
16+
import org.apache.logging.log4j.core.time.Instant;
17+
import org.apache.logging.log4j.message.Message;
18+
import org.apache.logging.log4j.message.StringMapMessage;
19+
import org.apache.logging.log4j.message.StructuredDataMessage;
20+
import org.apache.logging.log4j.util.ReadOnlyStringMap;
21+
22+
class LogEventToReplay implements LogEvent {
23+
24+
private static final long serialVersionUID = 1L;
25+
26+
// Log4j 2 reuses LogEvent object, so we make a copy of all the fields that are used during export
27+
// in order to be able to replay the log event later.
28+
29+
private final String loggerName;
30+
private final Message message;
31+
private final Level level;
32+
private final Instant instant;
33+
private final Throwable thrown;
34+
private final Marker marker;
35+
private final ReadOnlyStringMap contextData;
36+
private final String threadName;
37+
private final long threadId;
38+
39+
LogEventToReplay(LogEvent logEvent) {
40+
this.loggerName = logEvent.getLoggerName();
41+
Message messageOrigin = logEvent.getMessage();
42+
if (messageOrigin instanceof StructuredDataMessage) {
43+
StructuredDataMessage structuredDataMessage = (StructuredDataMessage) messageOrigin;
44+
this.message =
45+
// Log4j 2 reuses StructuredDataMessage object
46+
new StructuredDataMessage(
47+
structuredDataMessage.getId(),
48+
structuredDataMessage.getFormat(),
49+
structuredDataMessage.getType(),
50+
structuredDataMessage.getData());
51+
} else if (messageOrigin instanceof StringMapMessage) {
52+
// StringMapMessage objects are not reused by Log4j 2
53+
this.message = messageOrigin;
54+
} else {
55+
this.message = new MessageCopy(logEvent.getMessage());
56+
}
57+
58+
this.level = logEvent.getLevel();
59+
this.instant = logEvent.getInstant();
60+
this.thrown = logEvent.getThrown();
61+
this.marker = logEvent.getMarker();
62+
this.contextData = logEvent.getContextData();
63+
this.threadName = logEvent.getThreadName();
64+
this.threadId = logEvent.getThreadId();
65+
}
66+
67+
@Override
68+
public LogEvent toImmutable() {
69+
return null;
70+
}
71+
72+
@SuppressWarnings("deprecation") // Override
73+
@Override
74+
public Map<String, String> getContextMap() {
75+
return Collections.emptyMap();
76+
}
77+
78+
@Override
79+
public ReadOnlyStringMap getContextData() {
80+
return contextData;
81+
}
82+
83+
@Nullable
84+
@Override
85+
public ThreadContext.ContextStack getContextStack() {
86+
return null;
87+
}
88+
89+
@Override
90+
public String getLoggerFqcn() {
91+
return null;
92+
}
93+
94+
@Override
95+
public Level getLevel() {
96+
return level;
97+
}
98+
99+
@Override
100+
public String getLoggerName() {
101+
return loggerName;
102+
}
103+
104+
@Override
105+
public Marker getMarker() {
106+
return marker;
107+
}
108+
109+
@Override
110+
public Message getMessage() {
111+
return message;
112+
}
113+
114+
@Override
115+
public long getTimeMillis() {
116+
return 0;
117+
}
118+
119+
@Override
120+
public Instant getInstant() {
121+
return instant;
122+
}
123+
124+
@Override
125+
public StackTraceElement getSource() {
126+
return null;
127+
}
128+
129+
@Override
130+
public String getThreadName() {
131+
return threadName;
132+
}
133+
134+
@Override
135+
public long getThreadId() {
136+
return threadId;
137+
}
138+
139+
@Override
140+
public int getThreadPriority() {
141+
return 0;
142+
}
143+
144+
@Override
145+
public Throwable getThrown() {
146+
return thrown;
147+
}
148+
149+
@Override
150+
public ThrowableProxy getThrownProxy() {
151+
return null;
152+
}
153+
154+
@Override
155+
public boolean isEndOfBatch() {
156+
return false;
157+
}
158+
159+
@Override
160+
public boolean isIncludeLocation() {
161+
return false;
162+
}
163+
164+
@Override
165+
public void setEndOfBatch(boolean endOfBatch) {}
166+
167+
@Override
168+
public void setIncludeLocation(boolean locationRequired) {}
169+
170+
@Override
171+
public long getNanoTime() {
172+
return 0;
173+
}
174+
175+
private static class MessageCopy implements Message {
176+
177+
private static final long serialVersionUID = 1L;
178+
private final String formattedMessage;
179+
private final String format;
180+
private final Object[] parameters;
181+
private final Throwable throwable;
182+
183+
public MessageCopy(Message message) {
184+
this.formattedMessage = message.getFormattedMessage();
185+
this.format = message.getFormat();
186+
this.parameters = message.getParameters();
187+
this.throwable = message.getThrowable();
188+
}
189+
190+
@Override
191+
public String getFormattedMessage() {
192+
return formattedMessage;
193+
}
194+
195+
@Override
196+
public String getFormat() {
197+
return format;
198+
}
199+
200+
@Override
201+
public Object[] getParameters() {
202+
return parameters;
203+
}
204+
205+
@Override
206+
public Throwable getThrowable() {
207+
return throwable;
208+
}
209+
}
210+
}

0 commit comments

Comments
 (0)