33 * SPDX-License-Identifier: Apache-2.0
44 */
55
6- package io .opentelemetry .instrumentation .api .instrumenter ;
6+ import static io .opentelemetry .instrumentation .api .internal .SemconvExceptionSignal .emitExceptionAsLogs ;
7+ import static io .opentelemetry .instrumentation .api .internal .SemconvExceptionSignal .emitExceptionAsSpanEvents ;
78
89import io .opentelemetry .api .OpenTelemetry ;
10+ import io .opentelemetry .api .incubator .logs .ExtendedLogRecordBuilder ;
11+ import io .opentelemetry .api .logs .LogRecordBuilder ;
12+ import io .opentelemetry .api .logs .Logger ;
13+ import io .opentelemetry .api .logs .Severity ;
914import io .opentelemetry .api .trace .Span ;
1015import io .opentelemetry .api .trace .SpanBuilder ;
1116import io .opentelemetry .api .trace .SpanKind ;
1722import io .opentelemetry .instrumentation .api .internal .InstrumenterContext ;
1823import io .opentelemetry .instrumentation .api .internal .InstrumenterUtil ;
1924import io .opentelemetry .instrumentation .api .internal .SupportabilityMetrics ;
25+ import io .opentelemetry .semconv .ExceptionAttributes ;
26+ import java .io .PrintWriter ;
27+ import java .io .StringWriter ;
2028import java .time .Instant ;
2129import java .util .concurrent .TimeUnit ;
2230import javax .annotation .Nullable ;
@@ -71,6 +79,7 @@ public static <REQUEST, RESPONSE> InstrumenterBuilder<REQUEST, RESPONSE> builder
7179
7280 private final String instrumentationName ;
7381 private final Tracer tracer ;
82+ @ Nullable private final Logger logger ;
7483 private final SpanNameExtractor <? super REQUEST > spanNameExtractor ;
7584 private final SpanKindExtractor <? super REQUEST > spanKindExtractor ;
7685 private final SpanStatusExtractor <? super REQUEST , ? super RESPONSE > spanStatusExtractor ;
@@ -81,6 +90,7 @@ public static <REQUEST, RESPONSE> InstrumenterBuilder<REQUEST, RESPONSE> builder
8190 private final AttributesExtractor <? super REQUEST , ? super RESPONSE >[]
8291 operationListenerAttributesExtractors ;
8392 private final ErrorCauseExtractor errorCauseExtractor ;
93+ @ Nullable private final String exceptionEventName ;
8494 private final boolean propagateOperationListenersToOnEnd ;
8595 private final boolean enabled ;
8696 private final SpanSuppressor spanSuppressor ;
@@ -90,6 +100,7 @@ public static <REQUEST, RESPONSE> InstrumenterBuilder<REQUEST, RESPONSE> builder
90100 Instrumenter (InstrumenterBuilder <REQUEST , RESPONSE > builder ) {
91101 this .instrumentationName = builder .instrumentationName ;
92102 this .tracer = builder .buildTracer ();
103+ this .logger = emitExceptionAsLogs () ? builder .buildLogger () : null ;
93104 this .spanNameExtractor = builder .spanNameExtractor ;
94105 this .spanKindExtractor = builder .spanKindExtractor ;
95106 this .spanStatusExtractor = builder .spanStatusExtractor ;
@@ -100,6 +111,7 @@ public static <REQUEST, RESPONSE> InstrumenterBuilder<REQUEST, RESPONSE> builder
100111 this .operationListenerAttributesExtractors =
101112 builder .operationListenerAttributesExtractors .toArray (new AttributesExtractor [0 ]);
102113 this .errorCauseExtractor = builder .errorCauseExtractor ;
114+ this .exceptionEventName = builder .exceptionEventName ;
103115 this .propagateOperationListenersToOnEnd = builder .propagateOperationListenersToOnEnd ;
104116 this .enabled = builder .enabled ;
105117 this .spanSuppressor = builder .buildSpanSuppressor ();
@@ -259,7 +271,12 @@ private void doEnd(
259271
260272 if (error != null ) {
261273 error = errorCauseExtractor .extract (error );
262- span .recordException (error );
274+ if (emitExceptionAsSpanEvents ()) {
275+ span .recordException (error );
276+ }
277+ if (emitExceptionAsLogs ()) {
278+ emitExceptionLog (context , error );
279+ }
263280 }
264281
265282 UnsafeAttributes attributes = new UnsafeAttributes ();
@@ -300,6 +317,37 @@ private void doEnd(
300317 }
301318 }
302319
320+ private void emitExceptionLog (Context context , Throwable throwable ) {
321+ if (logger == null ) {
322+ // this condition is to keep nullaway happy
323+ // this can't happen since logger is non-null when stable exception semconv is enabled
324+ return ;
325+ }
326+ LogRecordBuilder logRecordBuilder = logger .logRecordBuilder ();
327+ logRecordBuilder .setContext (context );
328+ logRecordBuilder .setSeverity (Severity .ERROR );
329+ logRecordBuilder .setSeverityText ("ERROR" );
330+ if (exceptionEventName != null ) {
331+ logRecordBuilder .setEventName (exceptionEventName );
332+ }
333+
334+ if (logRecordBuilder instanceof ExtendedLogRecordBuilder ) {
335+ ((ExtendedLogRecordBuilder ) logRecordBuilder ).setException (throwable );
336+ } else {
337+ logRecordBuilder .setAttribute (
338+ ExceptionAttributes .EXCEPTION_TYPE , throwable .getClass ().getName ());
339+ String message = throwable .getMessage ();
340+ if (message != null ) {
341+ logRecordBuilder .setAttribute (ExceptionAttributes .EXCEPTION_MESSAGE , message );
342+ }
343+ StringWriter writer = new StringWriter ();
344+ throwable .printStackTrace (new PrintWriter (writer ));
345+ logRecordBuilder .setAttribute (ExceptionAttributes .EXCEPTION_STACKTRACE , writer .toString ());
346+ }
347+
348+ logRecordBuilder .emit ();
349+ }
350+
303351 private static long getNanos (@ Nullable Instant time ) {
304352 if (time == null ) {
305353 return System .nanoTime ();
0 commit comments