1818 */
1919package 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 ;
2134import org .apache .logging .log4j .Level ;
2235import org .apache .logging .log4j .core .config .Configurator ;
2336import org .apache .logging .log4j .core .config .builder .api .ConfigurationBuilder ;
2639
2740public 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}
0 commit comments