Skip to content

Commit 1255e39

Browse files
authored
implement spans debug logging (#704)
1 parent 535c501 commit 1255e39

File tree

4 files changed

+111
-14
lines changed

4 files changed

+111
-14
lines changed

custom/src/main/java/co/elastic/otel/ElasticAutoConfigurationCustomizerProvider.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import co.elastic.otel.dynamicconfig.BlockableMetricExporter;
2323
import co.elastic.otel.dynamicconfig.BlockableSpanExporter;
2424
import co.elastic.otel.dynamicconfig.CentralConfig;
25+
import co.elastic.otel.logging.AgentLog;
2526
import com.google.auto.service.AutoService;
2627
import io.opentelemetry.api.common.AttributeKey;
2728
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
@@ -72,6 +73,7 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) {
7273
autoConfiguration.addTracerProviderCustomizer(
7374
(providerBuilder, properties) -> {
7475
CentralConfig.init(providerBuilder, properties);
76+
AgentLog.addSpanLoggingIfRequired(providerBuilder, properties);
7577
return providerBuilder;
7678
});
7779
}

internal-logging/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ dependencies {
1010

1111
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-tooling")
1212
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-bootstrap")
13+
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
14+
compileOnly("io.opentelemetry:opentelemetry-exporter-logging")
15+
compileOnly("io.opentelemetry:opentelemetry-exporter-logging-otlp")
1316
compileOnly(libs.slf4j.api)
1417
implementation(libs.bundles.log4j2) {
1518
// Workaround for https://github.com/apache/logging-log4j2/issues/3754

internal-logging/src/main/java/co/elastic/otel/logging/AgentLog.java

Lines changed: 97 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@
1818
*/
1919
package co.elastic.otel.logging;
2020

21+
import static java.util.Collections.emptyList;
22+
23+
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
24+
import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingSpanExporter;
25+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
26+
import io.opentelemetry.sdk.common.CompletableResultCode;
27+
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
28+
import io.opentelemetry.sdk.trace.data.SpanData;
29+
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
30+
import io.opentelemetry.sdk.trace.export.SpanExporter;
31+
import java.util.Collection;
32+
import java.util.List;
33+
import java.util.concurrent.atomic.AtomicBoolean;
2134
import org.apache.logging.log4j.Level;
2235
import org.apache.logging.log4j.core.config.Configurator;
2336
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
@@ -26,14 +39,37 @@
2639

2740
public class AgentLog {
2841

42+
/** Upstream instrumentation debug boolean option */
43+
public static final String OTEL_JAVAAGENT_DEBUG = "otel.javaagent.debug";
44+
2945
private static final String PATTERN = "%d{DEFAULT} [%t] %-5level %logger{36} - %msg{nolookups}%n";
3046

31-
// logger is an empty string
47+
/** root logger is an empty string */
3248
private static final String ROOT_LOGGER_NAME = "";
3349

50+
private static boolean logPlainText = false;
51+
52+
/**
53+
* debug span logging exporter that can be controlled at runtime, only used when logging span
54+
* exporter has not been explicitly configured.
55+
*/
56+
private static DebugLogSpanExporter debugLogSpanExporter = null;
57+
3458
private AgentLog() {}
3559

36-
public static void init() {
60+
/**
61+
* Initializes agent logging
62+
*
63+
* @param usePlainTextLog {@literal true} to use plain text logging, `{@literal false} to use JSON
64+
* @param initialLevel initial log level to configure
65+
*/
66+
public static void init(boolean usePlainTextLog, Level initialLevel) {
67+
internalInit();
68+
setLevel(initialLevel);
69+
logPlainText = usePlainTextLog;
70+
}
71+
72+
private static void internalInit() {
3773

3874
ConfigurationBuilder<BuiltConfiguration> conf =
3975
ConfigurationBuilderFactory.newConfigurationBuilder();
@@ -47,6 +83,25 @@ public static void init() {
4783
Configurator.initialize(conf.build(false));
4884
}
4985

86+
public static void addSpanLoggingIfRequired(
87+
SdkTracerProviderBuilder providerBuilder, ConfigProperties config) {
88+
89+
// Replicate behavior of the upstream agent: span logging exporter is automatically added when
90+
// not already present when debugging. When logging exporter has been explicitly configured,
91+
// spans logging will be done by the explicitly configured logging exporter instance.
92+
93+
logPlainText = config.getBoolean(OTEL_JAVAAGENT_DEBUG, false);
94+
95+
List<String> exporters = config.getList("otel.traces.exporter", emptyList());
96+
if (!exporters.contains("logging") && !exporters.contains("otlp-logging")) {
97+
debugLogSpanExporter =
98+
new DebugLogSpanExporter(
99+
logPlainText ? LoggingSpanExporter.create() : OtlpJsonLoggingSpanExporter.create());
100+
101+
providerBuilder.addSpanProcessor(SimpleSpanProcessor.create(debugLogSpanExporter));
102+
}
103+
}
104+
50105
public static void setLevel(String level) {
51106
switch (level) {
52107
case "trace":
@@ -80,18 +135,55 @@ public static void setLevel(String level) {
80135
*
81136
* @param level log level
82137
*/
83-
public static void setLevel(Level level) {
138+
public static synchronized void setLevel(Level level) {
84139
// Using log4j2 implementation allows to change the log level programmatically at runtime
85140
// which is not directly possible through the slf4j API and simple implementation used in
86141
// upstream distribution.
87142

88143
Configurator.setAllLevels(ROOT_LOGGER_NAME, level);
89144

90-
// when debugging we should avoid very chatty http client debug messages
145+
boolean isDebug = level.intLevel() >= Level.DEBUG.intLevel();
146+
147+
// When debugging, we should avoid very chatty http client debug messages
91148
// this behavior is replicated from the upstream distribution.
92-
if (level.intLevel() >= Level.DEBUG.intLevel()) {
149+
if (isDebug) {
93150
Configurator.setLevel("okhttp3.internal.http2", Level.INFO);
94151
Configurator.setLevel("okhttp3.internal.concurrent.TaskRunner", Level.INFO);
95152
}
153+
154+
// when debugging the upstream otel agent configures an extra debug exporter
155+
if (debugLogSpanExporter != null) {
156+
debugLogSpanExporter.setEnabled(isDebug);
157+
}
158+
}
159+
160+
private static class DebugLogSpanExporter implements SpanExporter {
161+
162+
private final SpanExporter delegate;
163+
private final AtomicBoolean enabled;
164+
165+
DebugLogSpanExporter(SpanExporter delegate) {
166+
this.delegate = delegate;
167+
this.enabled = new AtomicBoolean(false);
168+
}
169+
170+
void setEnabled(boolean value) {
171+
enabled.set(value);
172+
}
173+
174+
@Override
175+
public CompletableResultCode export(Collection<SpanData> spans) {
176+
return enabled.get() ? delegate.export(spans) : CompletableResultCode.ofSuccess();
177+
}
178+
179+
@Override
180+
public CompletableResultCode flush() {
181+
return enabled.get() ? delegate.flush() : CompletableResultCode.ofSuccess();
182+
}
183+
184+
@Override
185+
public CompletableResultCode shutdown() {
186+
return enabled.get() ? delegate.shutdown() : CompletableResultCode.ofSuccess();
187+
}
96188
}
97189
}

internal-logging/src/main/java/co/elastic/otel/logging/ElasticLoggingCustomizer.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.opentelemetry.javaagent.bootstrap.InternalLogger;
2323
import io.opentelemetry.javaagent.tooling.LoggingCustomizer;
2424
import io.opentelemetry.javaagent.tooling.config.EarlyInitAgentConfig;
25+
import java.util.Optional;
2526
import org.apache.logging.log4j.Level;
2627
import org.slf4j.LoggerFactory;
2728

@@ -43,20 +44,19 @@ public void init(EarlyInitAgentConfig earlyConfig) {
4344
// make the agent internal logger delegate to slf4j, which will delegate to log4j
4445
InternalLogger.initialize(Slf4jInternalLogger::create);
4546

46-
AgentLog.init();
47-
48-
Level level = null;
49-
if (earlyConfig.getBoolean("otel.javaagent.debug", false)) {
47+
boolean upstreamDebugEnabled = earlyConfig.getBoolean(AgentLog.OTEL_JAVAAGENT_DEBUG, false);
48+
Level level;
49+
if (upstreamDebugEnabled) {
5050
// set debug logging when enabled through configuration to behave like the upstream
5151
// distribution
5252
level = Level.DEBUG;
5353
} else {
54-
String levelConfig = earlyConfig.getString("elastic.otel.javaagent.log.level");
55-
if (levelConfig != null) {
56-
level = Level.getLevel(levelConfig);
57-
}
54+
level =
55+
Optional.ofNullable(earlyConfig.getString("elastic.otel.javaagent.log.level"))
56+
.map(Level::getLevel)
57+
.orElse(Level.INFO);
5858
}
59-
AgentLog.setLevel(level != null ? level : Level.INFO);
59+
AgentLog.init(upstreamDebugEnabled, level);
6060
}
6161

6262
@Override

0 commit comments

Comments
 (0)