From 5593883f8da15a4b3541bf69655176005a1d1c9b Mon Sep 17 00:00:00 2001 From: Seoyoung Park Date: Wed, 15 Mar 2023 17:16:03 +0900 Subject: [PATCH] [#9631] Record SpanEventException in SpanRecorder.recordException() --- grpc/grpc-idl | 2 +- .../profiler/context/AsyncChildTrace.java | 12 +- .../profiler/context/AsyncDefaultTrace.java | 4 +- .../context/DefaultBaseTraceFactory.java | 10 +- .../profiler/context/DefaultTrace.java | 14 +- .../pinpoint/profiler/context/Span.java | 4 +- .../pinpoint/profiler/context/SpanEvent.java | 10 + .../exception/AtomicExceptionIdGenerator.java | 44 +++++ .../DefaultExceptionRecordingService.java | 55 ++++++ .../exception/ExceptionIdGenerator.java | 24 +++ .../exception/ExceptionRecordingContext.java | 50 +++++ .../exception/ExceptionRecordingService.java | 24 +++ .../ExceptionRecordingServiceProvider.java | 41 +++++ .../exception/ExceptionRecordingState.java | 116 ++++++++++++ .../context/exception/ExceptionWrapper.java | 94 ++++++++++ .../context/exception/SpanEventException.java | 81 +++++++++ .../exception/StackTraceElementWrapper.java | 106 +++++++++++ .../grpc/GrpcSpanMessageConverter.java | 71 ++++++++ .../module/ApplicationContextModule.java | 7 + .../context/recorder/AbstractRecorder.java | 12 +- .../recorder/DefaultRecorderFactory.java | 18 +- .../context/recorder/DefaultSpanRecorder.java | 17 +- .../recorder/WrappedSpanEventRecorder.java | 32 +++- .../profiler/context/DefaultTraceTest.java | 12 +- .../pinpoint/profiler/context/TraceTest.java | 19 +- .../recorder/DefaultSpanRecorderTest.java | 7 +- .../WrappedSpanEventRecorderTest.java | 15 +- .../DefaultExceptionRecordingServiceTest.java | 171 ++++++++++++++++++ 28 files changed, 1021 insertions(+), 51 deletions(-) create mode 100644 profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/AtomicExceptionIdGenerator.java create mode 100644 profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/DefaultExceptionRecordingService.java create mode 100644 profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionIdGenerator.java create mode 100644 profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingContext.java create mode 100644 profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingService.java create mode 100644 profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingServiceProvider.java create mode 100644 profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingState.java create mode 100644 profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionWrapper.java create mode 100644 profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/SpanEventException.java create mode 100644 profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/StackTraceElementWrapper.java create mode 100644 profiler/src/test/java/com/navercorp/pinpoint/profiler/metadata/DefaultExceptionRecordingServiceTest.java diff --git a/grpc/grpc-idl b/grpc/grpc-idl index 56f2b540105af..61ab44fdbab3c 160000 --- a/grpc/grpc-idl +++ b/grpc/grpc-idl @@ -1 +1 @@ -Subproject commit 56f2b540105afe04468b7b6c6febe4fd3cfcc8dd +Subproject commit 61ab44fdbab3c3abaf7f19e0955dd2906b3e2b99 diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/AsyncChildTrace.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/AsyncChildTrace.java index 79b0d94c7d8ed..bbbca33b0dc3d 100644 --- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/AsyncChildTrace.java +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/AsyncChildTrace.java @@ -23,6 +23,7 @@ import com.navercorp.pinpoint.bootstrap.context.scope.TraceScope; import com.navercorp.pinpoint.common.annotations.VisibleForTesting; import com.navercorp.pinpoint.exception.PinpointException; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingContext; import com.navercorp.pinpoint.profiler.context.id.TraceRoot; import com.navercorp.pinpoint.profiler.context.recorder.WrappedSpanEventRecorder; import com.navercorp.pinpoint.profiler.context.scope.DefaultTraceScopePool; @@ -46,6 +47,8 @@ public class AsyncChildTrace implements Trace { private final SpanRecorder spanRecorder; private final WrappedSpanEventRecorder wrappedSpanEventRecorder; + private final ExceptionRecordingContext exceptionRecordingContext; + private boolean closed = false; // lazy initialize private DefaultTraceScopePool scopePool; @@ -54,7 +57,9 @@ public class AsyncChildTrace implements Trace { private final LocalAsyncId localAsyncId; public AsyncChildTrace(final TraceRoot traceRoot, CallStack callStack, Storage storage, - SpanRecorder spanRecorder, WrappedSpanEventRecorder wrappedSpanEventRecorder, final LocalAsyncId localAsyncId) { + SpanRecorder spanRecorder, WrappedSpanEventRecorder wrappedSpanEventRecorder, + ExceptionRecordingContext exceptionRecordingContext, + final LocalAsyncId localAsyncId) { this.traceRoot = Objects.requireNonNull(traceRoot, "traceRoot"); this.callStack = Objects.requireNonNull(callStack, "callStack"); @@ -62,6 +67,7 @@ public AsyncChildTrace(final TraceRoot traceRoot, CallStack callStack this.spanRecorder = Objects.requireNonNull(spanRecorder, "spanRecorder"); this.wrappedSpanEventRecorder = Objects.requireNonNull(wrappedSpanEventRecorder, "wrappedSpanEventRecorder"); + this.exceptionRecordingContext = Objects.requireNonNull(exceptionRecordingContext, "exceptionRecordingContext"); this.localAsyncId = Objects.requireNonNull(localAsyncId, "localAsyncId"); traceBlockBegin(ASYNC_BEGIN_STACK_ID); @@ -73,7 +79,7 @@ private TraceRoot getTraceRoot() { } private SpanEventRecorder wrappedSpanEventRecorder(WrappedSpanEventRecorder wrappedSpanEventRecorder, SpanEvent spanEvent) { - wrappedSpanEventRecorder.setWrapped(spanEvent); + wrappedSpanEventRecorder.setWrapped(spanEvent, this.exceptionRecordingContext); return wrappedSpanEventRecorder; } @@ -149,7 +155,7 @@ public void traceBlockEnd(int stackId) { logSpan(spanEvent); // state restore final SpanEvent previous = callStack.peek(); - wrappedSpanEventRecorder.setWrapped(previous); + wrappedSpanEventRecorder(wrappedSpanEventRecorder, previous); } diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/AsyncDefaultTrace.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/AsyncDefaultTrace.java index 86dd16e2edada..0f12e0018d99b 100644 --- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/AsyncDefaultTrace.java +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/AsyncDefaultTrace.java @@ -2,6 +2,7 @@ import com.navercorp.pinpoint.bootstrap.context.AsyncState; import com.navercorp.pinpoint.bootstrap.context.SpanRecorder; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingContext; import com.navercorp.pinpoint.profiler.context.recorder.WrappedSpanEventRecorder; import com.navercorp.pinpoint.profiler.context.storage.Storage; @@ -15,8 +16,9 @@ public AsyncDefaultTrace(Span span, Storage storage, SpanRecorder spanRecorder, WrappedSpanEventRecorder wrappedSpanEventRecorder, + ExceptionRecordingContext exceptionRecordingContext, AsyncState asyncState) { - super(span, callStack, storage, spanRecorder, wrappedSpanEventRecorder, CloseListener.EMPTY); + super(span, callStack, storage, spanRecorder, wrappedSpanEventRecorder, exceptionRecordingContext, CloseListener.EMPTY); this.asyncState = Objects.requireNonNull(asyncState, "asyncState"); } diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/DefaultBaseTraceFactory.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/DefaultBaseTraceFactory.java index cf450659c4563..8b84212a40351 100644 --- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/DefaultBaseTraceFactory.java +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/DefaultBaseTraceFactory.java @@ -25,6 +25,7 @@ import com.navercorp.pinpoint.common.annotations.InterfaceAudience; import com.navercorp.pinpoint.profiler.context.active.ActiveTraceHandle; import com.navercorp.pinpoint.profiler.context.active.ActiveTraceRepository; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingContext; import com.navercorp.pinpoint.profiler.context.id.ListenableAsyncState; import com.navercorp.pinpoint.profiler.context.id.LocalTraceRoot; import com.navercorp.pinpoint.profiler.context.id.LoggingAsyncState; @@ -131,8 +132,9 @@ public Trace continueAsyncContextTraceObject(TraceRoot traceRoot, LocalAsyncId l final SpanRecorder spanRecorder = recorderFactory.newTraceRootSpanRecorder(traceRoot); final WrappedSpanEventRecorder wrappedSpanEventRecorder = recorderFactory.newWrappedSpanEventRecorder(traceRoot); + final ExceptionRecordingContext exceptionRecordingContext = ExceptionRecordingContext.newContext(); - return new AsyncChildTrace(traceRoot, callStack, storage, spanRecorder, wrappedSpanEventRecorder, localAsyncId); + return new AsyncChildTrace(traceRoot, callStack, storage, spanRecorder, wrappedSpanEventRecorder, exceptionRecordingContext, localAsyncId); } @Override @@ -197,10 +199,11 @@ private DefaultTrace newDefaultTrace(TraceRoot traceRoot) { final SpanRecorder spanRecorder = recorderFactory.newSpanRecorder(span); final WrappedSpanEventRecorder wrappedSpanEventRecorder = recorderFactory.newWrappedSpanEventRecorder(traceRoot); + final ExceptionRecordingContext exceptionRecordingContext = ExceptionRecordingContext.newContext(); final ActiveTraceHandle handle = registerActiveTrace(traceRoot); final CloseListener closeListener = new DefaultCloseListener(traceRoot, handle, uriStatStorage); - return new DefaultTrace(span, callStack, storage, spanRecorder, wrappedSpanEventRecorder, closeListener); + return new DefaultTrace(span, callStack, storage, spanRecorder, wrappedSpanEventRecorder, exceptionRecordingContext, closeListener); } private AsyncDefaultTrace newAsyncDefaultTrace(TraceRoot traceRoot) { @@ -215,8 +218,9 @@ private AsyncDefaultTrace newAsyncDefaultTrace(TraceRoot traceRoot) { final SpanRecorder spanRecorder = recorderFactory.newSpanRecorder(span); final WrappedSpanEventRecorder wrappedSpanEventRecorder = recorderFactory.newWrappedSpanEventRecorder(traceRoot, asyncState); + final ExceptionRecordingContext exceptionRecordingContext = ExceptionRecordingContext.newContext(); - return new AsyncDefaultTrace(span, callStack, storage, spanRecorder, wrappedSpanEventRecorder, asyncState); + return new AsyncDefaultTrace(span, callStack, storage, spanRecorder, wrappedSpanEventRecorder, exceptionRecordingContext, asyncState); } diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/DefaultTrace.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/DefaultTrace.java index 34db6f8cb272a..5e587861dc192 100644 --- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/DefaultTrace.java +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/DefaultTrace.java @@ -23,6 +23,7 @@ import com.navercorp.pinpoint.bootstrap.context.scope.TraceScope; import com.navercorp.pinpoint.common.annotations.VisibleForTesting; import com.navercorp.pinpoint.exception.PinpointException; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingContext; import com.navercorp.pinpoint.profiler.context.id.TraceRoot; import com.navercorp.pinpoint.profiler.context.recorder.WrappedSpanEventRecorder; import com.navercorp.pinpoint.profiler.context.scope.DefaultTraceScopePool; @@ -49,6 +50,8 @@ public class DefaultTrace implements Trace { private final SpanRecorder spanRecorder; private final WrappedSpanEventRecorder wrappedSpanEventRecorder; + private final ExceptionRecordingContext exceptionRecordingContext; + private boolean closed = false; // lazy initialize private DefaultTraceScopePool scopePool; @@ -58,13 +61,15 @@ public class DefaultTrace implements Trace { private final CloseListener closeListener; public DefaultTrace(Span span, CallStack callStack, Storage storage, - SpanRecorder spanRecorder, WrappedSpanEventRecorder wrappedSpanEventRecorder) { - this(span, callStack, storage, spanRecorder, wrappedSpanEventRecorder, CloseListener.EMPTY); + SpanRecorder spanRecorder, WrappedSpanEventRecorder wrappedSpanEventRecorder, + ExceptionRecordingContext exceptionRecordingContext) { + this(span, callStack, storage, spanRecorder, wrappedSpanEventRecorder, exceptionRecordingContext, CloseListener.EMPTY); } public DefaultTrace(Span span, CallStack callStack, Storage storage, SpanRecorder spanRecorder, WrappedSpanEventRecorder wrappedSpanEventRecorder, + ExceptionRecordingContext exceptionRecordingContext, CloseListener closeListener) { this.span = Objects.requireNonNull(span, "span"); @@ -73,6 +78,7 @@ public DefaultTrace(Span span, CallStack callStack, Storage storage, this.spanRecorder = Objects.requireNonNull(spanRecorder, "spanRecorder"); this.wrappedSpanEventRecorder = Objects.requireNonNull(wrappedSpanEventRecorder, "wrappedSpanEventRecorder"); + this.exceptionRecordingContext = Objects.requireNonNull(exceptionRecordingContext, "exceptionRecordingContext"); this.closeListener = closeListener; @@ -89,7 +95,7 @@ private TraceRoot getTraceRoot() { } private SpanEventRecorder wrappedSpanEventRecorder(WrappedSpanEventRecorder wrappedSpanEventRecorder, SpanEvent spanEvent) { - wrappedSpanEventRecorder.setWrapped(spanEvent); + wrappedSpanEventRecorder.setWrapped(spanEvent, this.exceptionRecordingContext); return wrappedSpanEventRecorder; } @@ -165,7 +171,7 @@ public void traceBlockEnd(int stackId) { logSpan(spanEvent); // state restore final SpanEvent previous = callStack.peek(); - wrappedSpanEventRecorder.setWrapped(previous); + wrappedSpanEventRecorder(wrappedSpanEventRecorder, previous); } diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/Span.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/Span.java index 611c6ca8346b5..f79bb54ff59a8 100644 --- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/Span.java +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/Span.java @@ -54,7 +54,6 @@ public class Span extends DefaultFrameAttachment implements SpanType { private IntStringValue exceptionInfo; // optional - public Span(final TraceRoot traceRoot) { this.traceRoot = Objects.requireNonNull(traceRoot, "traceRoot"); } @@ -159,7 +158,7 @@ public void markAfterTime() { } public void markAfterTime(long currentTime) { - final int after = (int)(currentTime - this.getStartTime()); + final int after = (int) (currentTime - this.getStartTime()); this.setElapsedTime(after); } @@ -175,7 +174,6 @@ public void setExceptionInfo(int exceptionClassId, String exceptionMessage) { this.setExceptionInfo(exceptionInfo); } - public boolean isTimeRecording() { return timeRecording; } diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/SpanEvent.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/SpanEvent.java index 115ef504af1a6..7db03ca27b7ff 100644 --- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/SpanEvent.java +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/SpanEvent.java @@ -17,6 +17,7 @@ package com.navercorp.pinpoint.profiler.context; import com.navercorp.pinpoint.common.util.IntStringValue; +import com.navercorp.pinpoint.profiler.context.exception.SpanEventException; import java.util.ArrayList; import java.util.List; @@ -49,6 +50,7 @@ public class SpanEvent extends DefaultFrameAttachment { private int apiId; // optional private IntStringValue exceptionInfo; // optional + private SpanEventException flushedException; // optional private AsyncId asyncIdObject; @@ -66,6 +68,14 @@ public void setExceptionInfo(int exceptionClassId, String exceptionMessage) { this.exceptionInfo = new IntStringValue(exceptionClassId, exceptionMessage); } + public SpanEventException getFlushedException() { + return flushedException; + } + + public void setFlushedException(SpanEventException flushedException) { + this.flushedException = flushedException; + } + public void markStartTime() { setStartTime(System.currentTimeMillis()); } diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/AtomicExceptionIdGenerator.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/AtomicExceptionIdGenerator.java new file mode 100644 index 0000000000000..21bf370ec6511 --- /dev/null +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/AtomicExceptionIdGenerator.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.profiler.context.exception; + +import com.google.inject.Inject; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * @author intr3p1d + */ +public class AtomicExceptionIdGenerator implements ExceptionIdGenerator { + + public static final long INITIAL_EXCEPTION_ID = 1L; + + private final AtomicLong errorId = new AtomicLong(INITIAL_EXCEPTION_ID); + + @Inject + public AtomicExceptionIdGenerator() { + } + + @Override + public long getCurrentExceptionId() { + return this.errorId.get(); + } + + @Override + public long nextExceptionId() { + return this.errorId.getAndIncrement(); + } +} diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/DefaultExceptionRecordingService.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/DefaultExceptionRecordingService.java new file mode 100644 index 0000000000000..5874315f8b707 --- /dev/null +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/DefaultExceptionRecordingService.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.profiler.context.exception; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Objects; + +/** + * @author intr3p1d + */ +public class DefaultExceptionRecordingService implements ExceptionRecordingService { + + private static final Logger logger = LogManager.getLogger(DefaultExceptionRecordingService.class); + + private static final boolean IS_DEBUG = logger.isDebugEnabled(); + + private final ExceptionIdGenerator exceptionIdGenerator; + + public DefaultExceptionRecordingService(ExceptionIdGenerator exceptionIdGenerator) { + this.exceptionIdGenerator = exceptionIdGenerator; + } + + @Override + public SpanEventException recordException(ExceptionRecordingContext context, Throwable current, long startTime) { + Objects.requireNonNull(context); + + ExceptionRecordingState state = ExceptionRecordingState.stateOf(context.getPrevious(), current); + SpanEventException spanEventException = state.apply(context, current, startTime, exceptionIdGenerator); + + logException(spanEventException); + + return spanEventException; + } + + private void logException(SpanEventException spanEventException) { + if (IS_DEBUG && spanEventException != null) { + logger.debug(spanEventException); + } + } +} diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionIdGenerator.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionIdGenerator.java new file mode 100644 index 0000000000000..681d0a0504332 --- /dev/null +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionIdGenerator.java @@ -0,0 +1,24 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.profiler.context.exception; + +/** + * @author intr3p1d + */ +public interface ExceptionIdGenerator { + long getCurrentExceptionId(); + long nextExceptionId(); +} diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingContext.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingContext.java new file mode 100644 index 0000000000000..3c8c35bdb4920 --- /dev/null +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingContext.java @@ -0,0 +1,50 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.profiler.context.exception; + +import javax.annotation.Nullable; + +/** + * @author intr3p1d + */ +public class ExceptionRecordingContext { + private Throwable previous = null; + private long startTime = 0; + + public static ExceptionRecordingContext newContext() { + return new ExceptionRecordingContext(); + } + + public void resetStartTime() { + setStartTime(0); + } + + public Throwable getPrevious() { + return previous; + } + + public void setPrevious(@Nullable Throwable previous) { + this.previous = previous; + } + + public long getStartTime() { + return startTime; + } + + public void setStartTime(long startTime) { + this.startTime = startTime; + } +} diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingService.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingService.java new file mode 100644 index 0000000000000..460541e8e4ae5 --- /dev/null +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingService.java @@ -0,0 +1,24 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.profiler.context.exception; + +/** + * @author intr3p1d + */ +public interface ExceptionRecordingService { + SpanEventException recordException(ExceptionRecordingContext context, Throwable current, long startTime); + +} diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingServiceProvider.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingServiceProvider.java new file mode 100644 index 0000000000000..f08161b44bd4d --- /dev/null +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingServiceProvider.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.profiler.context.exception; + +import com.google.inject.Inject; +import com.navercorp.pinpoint.bootstrap.config.ProfilerConfig; + +import javax.inject.Provider; + +/** + * @author intr3p1d + */ +public class ExceptionRecordingServiceProvider implements Provider { + + private final ProfilerConfig profilerConfig; + private final ExceptionIdGenerator exceptionIdGenerator; + + @Inject + public ExceptionRecordingServiceProvider(ProfilerConfig profilerConfig, ExceptionIdGenerator exceptionIdGenerator) { + this.profilerConfig = profilerConfig; + this.exceptionIdGenerator = exceptionIdGenerator; + } + + @Override + public ExceptionRecordingService get() { + return new DefaultExceptionRecordingService(exceptionIdGenerator); + } +} diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingState.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingState.java new file mode 100644 index 0000000000000..501e98a81c370 --- /dev/null +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionRecordingState.java @@ -0,0 +1,116 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.profiler.context.exception; + +/** + * @author intr3p1d + */ +public enum ExceptionRecordingState { + CLEAN { + @Override + public SpanEventException apply( + ExceptionRecordingContext exceptionRecordingContext, + Throwable current, + long currentStartTime, + ExceptionIdGenerator idGenerator) { + // do nothing + return null; + } + }, + STARTED { + @Override + public SpanEventException apply( + ExceptionRecordingContext exceptionRecordingContext, + Throwable current, + long currentStartTime, + ExceptionIdGenerator idGenerator) { + exceptionRecordingContext.setPrevious(current); + exceptionRecordingContext.setStartTime(currentStartTime); + return null; + } + }, + STACKING { + @Override + public SpanEventException apply( + ExceptionRecordingContext exceptionRecordingContext, + Throwable current, + long currentStartTime, + ExceptionIdGenerator idGenerator) { + SpanEventException spanEventException = null; + if (!isContinued(exceptionRecordingContext.getPrevious(), current)) { + spanEventException = newSpanEventException( + exceptionRecordingContext, idGenerator.nextExceptionId() + ); + } + exceptionRecordingContext.setPrevious(current); + exceptionRecordingContext.setStartTime(currentStartTime); + return spanEventException; + } + }, + FLUSH { + @Override + public SpanEventException apply( + ExceptionRecordingContext exceptionRecordingContext, + Throwable current, + long currentStartTime, + ExceptionIdGenerator idGenerator) { + SpanEventException spanEventException = newSpanEventException( + exceptionRecordingContext, idGenerator.nextExceptionId() + ); + exceptionRecordingContext.setPrevious(current); + exceptionRecordingContext.resetStartTime(); + return spanEventException; + } + }; + + public static ExceptionRecordingState stateOf(Object previous, Object current) { + if (previous == null) { + if (current == null) { + return CLEAN; + } + return STARTED; + } else { + if (current == null) { + return FLUSH; + } + return STACKING; + } + } + + public static SpanEventException newSpanEventException(ExceptionRecordingContext context, long exceptionId) { + return SpanEventException.newSpanEventException( + context.getPrevious(), context.getStartTime(), exceptionId + ); + } + + public static boolean isContinued(Throwable previous, Throwable current) { + Throwable throwable = current; + while (throwable.getCause() != null) { + if (throwable == previous) { + return true; + } + throwable = throwable.getCause(); + } + return false; + } + + public abstract SpanEventException apply( + ExceptionRecordingContext exceptionRecordingContext, + Throwable current, + long currentStartTime, + ExceptionIdGenerator idGenerator + ); +} diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionWrapper.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionWrapper.java new file mode 100644 index 0000000000000..f27ff6e6745bd --- /dev/null +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/ExceptionWrapper.java @@ -0,0 +1,94 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.profiler.context.exception; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * @author intr3p1d + */ +public class ExceptionWrapper { + private final String exceptionClassName; + private final String exceptionMessage; + private final StackTraceElementWrapper[] stackTraceElements; + + private ExceptionWrapper(Throwable throwable){ + Objects.requireNonNull(throwable); + this.exceptionClassName = throwable.getClass().getSimpleName(); + this.exceptionMessage = throwable.getMessage(); + this.stackTraceElements = StackTraceElementWrapper.valueOf(throwable.getStackTrace()); + } + + public static ExceptionWrapper[] newExceptions(Throwable throwable) { + if (throwable == null) { + return new ExceptionWrapper[0]; + } + List exceptionWrappers = new ArrayList<>(); + Throwable curr = throwable; + while (curr.getCause() != null) { + exceptionWrappers.add(new ExceptionWrapper(curr)); + curr = curr.getCause(); + } + return exceptionWrappers.toArray(new ExceptionWrapper[0]); + } + + + public String getExceptionClassName() { + return exceptionClassName; + } + + public String getExceptionMessage() { + return exceptionMessage; + } + + public StackTraceElementWrapper[] getStackTraceElements() { + return stackTraceElements; + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ExceptionWrapper that = (ExceptionWrapper) o; + + if (!exceptionClassName.equals(that.exceptionClassName)) return false; + if (!exceptionMessage.equals(that.exceptionMessage)) return false; + // Probably incorrect - comparing Object[] arrays with Arrays.equals + return Arrays.equals(stackTraceElements, that.stackTraceElements); + } + + @Override + public int hashCode() { + int result = exceptionClassName.hashCode(); + result = 31 * result + exceptionMessage.hashCode(); + result = 31 * result + Arrays.hashCode(stackTraceElements); + return result; + } + + @Override + public String toString() { + return "ExceptionWrapper{" + + "exceptionClassName='" + exceptionClassName + '\'' + + ", exceptionMessage='" + exceptionMessage + '\'' + + ", stackTraceElements=" + Arrays.toString(stackTraceElements) + + '}'; + } +} diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/SpanEventException.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/SpanEventException.java new file mode 100644 index 0000000000000..fd39e0f4b5dae --- /dev/null +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/SpanEventException.java @@ -0,0 +1,81 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.profiler.context.exception; + +import java.util.Arrays; +import java.util.Objects; + +/** + * @author intr3p1d + */ +public class SpanEventException { + + private final ExceptionWrapper[] exceptionWrappers; + + private final long startTime; + + private final long exceptionId; + + private SpanEventException(Throwable throwable, long startTime, long exceptionId) { + Objects.requireNonNull(throwable); + this.exceptionWrappers = ExceptionWrapper.newExceptions(throwable); + this.startTime = startTime; + this.exceptionId = exceptionId; + } + + public static SpanEventException newSpanEventException(Throwable throwable, long startTime, long exceptionId) { + return new SpanEventException(throwable, startTime, exceptionId); + } + + public long getStartTime() { + return startTime; + } + + public ExceptionWrapper[] getExceptionWrappers() { + return exceptionWrappers; + } + + public long getExceptionId() { + return exceptionId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SpanEventException that = (SpanEventException) o; + + if (startTime != that.startTime) return false; + // Probably incorrect - comparing Object[] arrays with Arrays.equals + return Arrays.equals(exceptionWrappers, that.exceptionWrappers); + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(exceptionWrappers); + result = 31 * result + (int) (startTime ^ (startTime >>> 32)); + return result; + } + + @Override + public String toString() { + return "SpanEventException{" + + "exceptionWrappers=" + Arrays.toString(exceptionWrappers) + + ", startTime=" + startTime + + '}'; + } +} diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/StackTraceElementWrapper.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/StackTraceElementWrapper.java new file mode 100644 index 0000000000000..103bb9e9e5d03 --- /dev/null +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/exception/StackTraceElementWrapper.java @@ -0,0 +1,106 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.profiler.context.exception; + +import java.util.Optional; + +/** + * @author intr3p1d + */ +public class StackTraceElementWrapper { + + private static final String EMPTY_STRING = ""; + private final String className; + private final String fileName; + private final int lineNumber; + private final String methodName; + + private StackTraceElementWrapper(String className, + String fileName, + int lineNumber, + String methodName) { + this.className = Optional.ofNullable(className).orElse(EMPTY_STRING); + this.fileName = Optional.ofNullable(fileName).orElse(EMPTY_STRING); + this.lineNumber = lineNumber; + this.methodName = Optional.ofNullable(methodName).orElse(EMPTY_STRING); + } + + protected static StackTraceElementWrapper[] valueOf(StackTraceElement[] stackTraceElements) { + final int size = stackTraceElements.length; + StackTraceElementWrapper[] stackTraceElementWrappers = new StackTraceElementWrapper[size]; + for (int i = 0; i < size; i++) { + stackTraceElementWrappers[i] = valueOf(stackTraceElements[i]); + } + return stackTraceElementWrappers; + } + + public static StackTraceElementWrapper valueOf(StackTraceElement stackTraceElement) { + return new StackTraceElementWrapper( + stackTraceElement.getClassName(), + stackTraceElement.getFileName(), + stackTraceElement.getLineNumber(), + stackTraceElement.getMethodName() + ); + } + + public String getClassName() { + return className; + } + + public String getFileName() { + return fileName; + } + + public int getLineNumber() { + return lineNumber; + } + + public String getMethodName() { + return methodName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + StackTraceElementWrapper that = (StackTraceElementWrapper) o; + + if (lineNumber != that.lineNumber) return false; + if (!className.equals(that.className)) return false; + if (!fileName.equals(that.fileName)) return false; + return methodName.equals(that.methodName); + } + + @Override + public int hashCode() { + int result = className.hashCode(); + result = 31 * result + fileName.hashCode(); + result = 31 * result + lineNumber; + result = 31 * result + methodName.hashCode(); + return result; + } + + @Override + public String toString() { + return "StackTraceElementWrapper{" + + "className='" + className + '\'' + + ", fileName='" + fileName + '\'' + + ", lineNumber=" + lineNumber + + ", methodName='" + methodName + '\'' + + '}'; + } +} diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/grpc/GrpcSpanMessageConverter.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/grpc/GrpcSpanMessageConverter.java index e49911d49c77e..7c4c2940b3fc0 100644 --- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/grpc/GrpcSpanMessageConverter.java +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/grpc/GrpcSpanMessageConverter.java @@ -27,6 +27,7 @@ import com.navercorp.pinpoint.grpc.trace.PAcceptEvent; import com.navercorp.pinpoint.grpc.trace.PAnnotation; import com.navercorp.pinpoint.grpc.trace.PAnnotationValue; +import com.navercorp.pinpoint.grpc.trace.PException; import com.navercorp.pinpoint.grpc.trace.PIntStringValue; import com.navercorp.pinpoint.grpc.trace.PLocalAsyncId; import com.navercorp.pinpoint.grpc.trace.PMessageEvent; @@ -35,6 +36,8 @@ import com.navercorp.pinpoint.grpc.trace.PSpan; import com.navercorp.pinpoint.grpc.trace.PSpanChunk; import com.navercorp.pinpoint.grpc.trace.PSpanEvent; +import com.navercorp.pinpoint.grpc.trace.PSpanEventException; +import com.navercorp.pinpoint.grpc.trace.PStackTraceElement; import com.navercorp.pinpoint.grpc.trace.PTransactionId; import com.navercorp.pinpoint.io.SpanVersion; import com.navercorp.pinpoint.profiler.context.Annotation; @@ -46,6 +49,9 @@ import com.navercorp.pinpoint.profiler.context.SpanEvent; import com.navercorp.pinpoint.profiler.context.SpanType; import com.navercorp.pinpoint.profiler.context.compress.SpanProcessor; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionWrapper; +import com.navercorp.pinpoint.profiler.context.exception.SpanEventException; +import com.navercorp.pinpoint.profiler.context.exception.StackTraceElementWrapper; import com.navercorp.pinpoint.profiler.context.id.Shared; import com.navercorp.pinpoint.profiler.context.id.TraceRoot; import com.navercorp.pinpoint.profiler.context.thrift.MessageConverter; @@ -53,6 +59,7 @@ import org.apache.logging.log4j.Logger; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -77,6 +84,8 @@ public class GrpcSpanMessageConverter implements MessageConverter tAnnotations = buildPAnnotation(annotations); pSpan.addAllAnnotation(tAnnotations); } + this.spanProcessor.preProcess(span, pSpan); final List spanEventList = span.getSpanEventList(); if (CollectionUtils.hasLength(spanEventList)) { @@ -341,6 +351,12 @@ public PSpanEvent.Builder buildPSpanEvent(SpanEvent spanEvent) { pSpanEvent.addAllAnnotation(pAnnotations); } + final SpanEventException spanEventException = spanEvent.getFlushedException(); + if (spanEventException != null) { + final PSpanEventException pSpanEventException = buildPSpanEventException(spanEventException).build(); + pSpanEvent.setFlushedException(pSpanEventException); + } + return pSpanEvent; } @@ -379,6 +395,51 @@ private PMessageEvent.Builder newPMessageEvent(PMessageEvent.Builder builder) { return builder; } + private PSpanEventException.Builder buildPSpanEventException(SpanEventException spanEventException) { + final PSpanEventException.Builder pSpanEventException = getSpanEventExceptionBuilder(); + if (spanEventException.getExceptionWrappers() != null) { + pSpanEventException.addAllExceptions(buildPExceptions(Arrays.asList(spanEventException.getExceptionWrappers()))); + } + pSpanEventException.setStartTime(spanEventException.getStartTime()); + pSpanEventException.setExceptionId(spanEventException.getExceptionId()); + return pSpanEventException; + } + + List buildPExceptions(List exceptionWrappers) { + final List tExceptionList = new ArrayList<>(exceptionWrappers.size()); + for (ExceptionWrapper exceptionWrapper : exceptionWrappers) { + final PException pException = buildException(exceptionWrapper).build(); + tExceptionList.add(pException); + } + return tExceptionList; + } + + private PException.Builder buildException(ExceptionWrapper exceptionWrapper) { + final PException.Builder pException = getExceptionBuilder(); + pException.setExceptionClassName(exceptionWrapper.getExceptionClassName()); + pException.setExceptionMessage(exceptionWrapper.getExceptionMessage()); + + final StackTraceElementWrapper[] stackTraceElementWrappers = exceptionWrapper.getStackTraceElements(); + if (stackTraceElementWrappers != null && stackTraceElementWrappers.length > 0) { + final List pStackTraceElements = new ArrayList<>(); + for (StackTraceElementWrapper stackTraceElementWrapper : stackTraceElementWrappers) { + pStackTraceElements.add(buildStackTraceElement(stackTraceElementWrapper)); + } + pException.addAllStackTraceElement(pStackTraceElements); + } + + return pException; + } + + private PStackTraceElement buildStackTraceElement(StackTraceElementWrapper stackTraceElementWrapper) { + final PStackTraceElement.Builder builder = PStackTraceElement.newBuilder(); + builder.setClassName(stackTraceElementWrapper.getClassName()); + builder.setFileName(stackTraceElementWrapper.getFileName()); + builder.setLineNumber(stackTraceElementWrapper.getLineNumber()); + builder.setMethodName(stackTraceElementWrapper.getMethodName()); + return builder.build(); + } + private PIntStringValue buildPIntStringValue(IntStringValue exceptionInfo) { PIntStringValue.Builder builder = PIntStringValue.newBuilder(); builder.setIntValue(exceptionInfo.getIntValue()); @@ -415,6 +476,16 @@ private PSpanEvent.Builder getSpanEventBuilder() { return pSpanEventBuilder; } + private PSpanEventException.Builder getSpanEventExceptionBuilder() { + pSpanEventExceptionBuilder.clear(); + return pSpanEventExceptionBuilder; + } + + private PException.Builder getExceptionBuilder() { + pExceptionBuilder.clear(); + return pExceptionBuilder; + } + @Override public String toString() { return "GrpcSpanMessageConverter{" + diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/module/ApplicationContextModule.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/module/ApplicationContextModule.java index bc75d1dbd92a0..a86c345697197 100644 --- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/module/ApplicationContextModule.java +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/module/ApplicationContextModule.java @@ -28,6 +28,10 @@ import com.navercorp.pinpoint.common.trace.ServiceType; import com.navercorp.pinpoint.profiler.AgentInfoSender; import com.navercorp.pinpoint.profiler.AgentInformation; +import com.navercorp.pinpoint.profiler.context.exception.AtomicExceptionIdGenerator; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionIdGenerator; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingService; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingServiceProvider; import com.navercorp.pinpoint.profiler.context.provider.BindVariableServiceProvider; import com.navercorp.pinpoint.profiler.context.provider.UriStatStorageProvider; import com.navercorp.pinpoint.profiler.context.storage.UriStatStorage; @@ -165,6 +169,8 @@ protected void configure() { bind(AsyncIdGenerator.class).to(DefaultAsyncIdGenerator.class).in(Scopes.SINGLETON); bind(TransactionCounter.class).to(DefaultTransactionCounter.class).in(Scopes.SINGLETON); + bind(ExceptionIdGenerator.class).to(AtomicExceptionIdGenerator.class).in(Scopes.SINGLETON); + bind(Sampler.class).toProvider(SamplerProvider.class).in(Scopes.SINGLETON); bind(TraceSampler.class).toProvider(TraceSamplerProvider.class).in(Scopes.SINGLETON); @@ -238,6 +244,7 @@ private void bindServiceComponent() { bind(StringMetaDataService.class).toProvider(StringMetadataServiceProvider.class).in(Scopes.SINGLETON); bind(ApiMetaDataService.class).toProvider(ApiMetaDataServiceProvider.class).in(Scopes.SINGLETON); bind(SqlMetaDataService.class).toProvider(SqlMetadataServiceProvider.class).in(Scopes.SINGLETON); + bind(ExceptionRecordingService.class).toProvider(ExceptionRecordingServiceProvider.class).in(Scopes.SINGLETON); bind(PredefinedMethodDescriptorRegistry.class).to(DefaultPredefinedMethodDescriptorRegistry.class).in(Scopes.SINGLETON); } diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/AbstractRecorder.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/AbstractRecorder.java index 422f38d3ef06e..98ea56bb93518 100644 --- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/AbstractRecorder.java +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/AbstractRecorder.java @@ -19,6 +19,7 @@ import com.navercorp.pinpoint.bootstrap.context.MethodDescriptor; import com.navercorp.pinpoint.common.trace.AnnotationKey; import com.navercorp.pinpoint.common.util.AnnotationKeyUtils; + import java.util.Objects; import com.navercorp.pinpoint.common.util.DataType; @@ -26,6 +27,7 @@ import com.navercorp.pinpoint.profiler.context.Annotation; import com.navercorp.pinpoint.profiler.context.annotation.Annotations; import com.navercorp.pinpoint.profiler.context.errorhandler.IgnoreErrorHandler; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingService; import com.navercorp.pinpoint.profiler.metadata.SqlMetaDataService; import com.navercorp.pinpoint.profiler.metadata.StringMetaDataService; @@ -37,11 +39,16 @@ public abstract class AbstractRecorder implements AttributeRecorder { protected final StringMetaDataService stringMetaDataService; protected final SqlMetaDataService sqlMetaDataService; protected final IgnoreErrorHandler ignoreErrorHandler; + protected final ExceptionRecordingService exceptionRecordingService; - public AbstractRecorder(final StringMetaDataService stringMetaDataService, SqlMetaDataService sqlMetaDataService, IgnoreErrorHandler ignoreErrorHandler) { + public AbstractRecorder(final StringMetaDataService stringMetaDataService, + SqlMetaDataService sqlMetaDataService, + IgnoreErrorHandler ignoreErrorHandler, + ExceptionRecordingService exceptionRecordingService) { this.stringMetaDataService = Objects.requireNonNull(stringMetaDataService, "stringMetaDataService"); this.sqlMetaDataService = Objects.requireNonNull(sqlMetaDataService, "sqlMetaDataService"); this.ignoreErrorHandler = Objects.requireNonNull(ignoreErrorHandler, "ignoreErrorHandler"); + this.exceptionRecordingService = Objects.requireNonNull(exceptionRecordingService, "exceptionRecordingService"); } public void recordError() { @@ -53,6 +60,7 @@ public void recordException(Throwable throwable) { } public void recordException(boolean markError, Throwable throwable) { + recordDetailedException(throwable); if (throwable == null) { return; } @@ -67,6 +75,8 @@ public void recordException(boolean markError, Throwable throwable) { } } + abstract void recordDetailedException(Throwable throwable); + abstract void setExceptionInfo(int exceptionClassId, String exceptionMessage); abstract void maskErrorCode(final int errorCode); diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/DefaultRecorderFactory.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/DefaultRecorderFactory.java index 05a729fd6dcef..b6cc1b8eb0051 100644 --- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/DefaultRecorderFactory.java +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/DefaultRecorderFactory.java @@ -23,8 +23,10 @@ import com.navercorp.pinpoint.profiler.context.AsyncContextFactory; import com.navercorp.pinpoint.profiler.context.Span; import com.navercorp.pinpoint.profiler.context.errorhandler.IgnoreErrorHandler; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingService; import com.navercorp.pinpoint.profiler.context.id.LocalTraceRoot; import com.navercorp.pinpoint.profiler.context.id.TraceRoot; +import com.navercorp.pinpoint.profiler.context.exception.DefaultExceptionRecordingService; import com.navercorp.pinpoint.profiler.metadata.SqlMetaDataService; import com.navercorp.pinpoint.profiler.metadata.StringMetaDataService; @@ -40,20 +42,26 @@ public class DefaultRecorderFactory implements RecorderFactory { private final Provider asyncContextFactoryProvider; private final IgnoreErrorHandler errorHandler; + private final ExceptionRecordingService exceptionRecordingService; + @Inject public DefaultRecorderFactory(Provider asyncContextFactoryProvider, - StringMetaDataService stringMetaDataService, SqlMetaDataService sqlMetaDataService, IgnoreErrorHandler errorHandler) { + StringMetaDataService stringMetaDataService, + SqlMetaDataService sqlMetaDataService, + IgnoreErrorHandler errorHandler, + ExceptionRecordingService exceptionRecordingService) { this.asyncContextFactoryProvider = Objects.requireNonNull(asyncContextFactoryProvider, "asyncContextFactoryProvider"); this.stringMetaDataService = Objects.requireNonNull(stringMetaDataService, "stringMetaDataService"); this.sqlMetaDataService = Objects.requireNonNull(sqlMetaDataService, "sqlMetaDataService"); this.errorHandler = Objects.requireNonNull(errorHandler, "errorHandler"); + this.exceptionRecordingService = Objects.requireNonNull(exceptionRecordingService, "exceptionRecordingService"); } @Override public SpanRecorder newSpanRecorder(Span span) { Objects.requireNonNull(span, "span"); - return new DefaultSpanRecorder(span, stringMetaDataService, sqlMetaDataService, errorHandler); + return new DefaultSpanRecorder(span, stringMetaDataService, sqlMetaDataService, errorHandler, exceptionRecordingService); } @Override @@ -75,7 +83,8 @@ public WrappedSpanEventRecorder newWrappedSpanEventRecorder(TraceRoot traceRoot) Objects.requireNonNull(traceRoot, "traceRoot"); final AsyncContextFactory asyncContextFactory = asyncContextFactoryProvider.get(); - return new WrappedSpanEventRecorder(traceRoot, asyncContextFactory, stringMetaDataService, sqlMetaDataService, errorHandler); + return new WrappedSpanEventRecorder(traceRoot, asyncContextFactory, + stringMetaDataService, sqlMetaDataService, errorHandler, exceptionRecordingService); } @Override @@ -84,7 +93,8 @@ public WrappedSpanEventRecorder newWrappedSpanEventRecorder(TraceRoot traceRoot, Objects.requireNonNull(asyncState, "asyncState"); final AsyncContextFactory asyncContextFactory = asyncContextFactoryProvider.get(); - return new WrappedSpanEventRecorder(traceRoot, asyncContextFactory, asyncState, stringMetaDataService, sqlMetaDataService, errorHandler); + return new WrappedSpanEventRecorder(traceRoot, asyncContextFactory, asyncState, + stringMetaDataService, sqlMetaDataService, errorHandler, exceptionRecordingService); } @Override diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/DefaultSpanRecorder.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/DefaultSpanRecorder.java index 1df25970fa5b8..9a5db7a2284eb 100644 --- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/DefaultSpanRecorder.java +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/DefaultSpanRecorder.java @@ -21,6 +21,7 @@ import com.navercorp.pinpoint.profiler.context.Annotation; import com.navercorp.pinpoint.profiler.context.Span; import com.navercorp.pinpoint.profiler.context.errorhandler.IgnoreErrorHandler; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingService; import com.navercorp.pinpoint.profiler.context.id.Shared; import com.navercorp.pinpoint.profiler.metadata.SqlMetaDataService; import com.navercorp.pinpoint.profiler.metadata.StringMetaDataService; @@ -28,9 +29,7 @@ import org.apache.logging.log4j.Logger; /** - * * @author jaehong.kim - * */ public class DefaultSpanRecorder extends AbstractRecorder implements SpanRecorder { private static final Logger logger = LogManager.getLogger(DefaultSpanRecorder.class); @@ -41,8 +40,9 @@ public class DefaultSpanRecorder extends AbstractRecorder implements SpanRecorde public DefaultSpanRecorder(final Span span, final StringMetaDataService stringMetaDataService, final SqlMetaDataService sqlMetaDataService, - final IgnoreErrorHandler errorHandler) { - super(stringMetaDataService, sqlMetaDataService, errorHandler); + final IgnoreErrorHandler errorHandler, + final ExceptionRecordingService exceptionRecordingService) { + super(stringMetaDataService, sqlMetaDataService, errorHandler, exceptionRecordingService); this.span = span; } @@ -57,6 +57,11 @@ void setExceptionInfo(int exceptionClassId, String exceptionMessage) { span.setExceptionInfo(exceptionClassId, exceptionMessage); } + @Override + void recordDetailedException(Throwable throwable) { + // do nothing + } + @Override void maskErrorCode(final int errorCode) { getShared().maskErrorCode(errorCode); @@ -124,12 +129,12 @@ public boolean canSampled() { public boolean isRoot() { return span.getTraceRoot().getTraceId().isRoot(); } - + @Override public void recordLogging(LoggingInfo loggingInfo) { getShared().setLoggingInfo(loggingInfo.getCode()); } - + @Override public void recordTime(boolean autoTimeRecoding) { span.setTimeRecording(autoTimeRecoding); diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/WrappedSpanEventRecorder.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/WrappedSpanEventRecorder.java index ec24a8801333c..bbef07822216f 100644 --- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/WrappedSpanEventRecorder.java +++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/recorder/WrappedSpanEventRecorder.java @@ -30,6 +30,9 @@ import com.navercorp.pinpoint.profiler.context.AsyncId; import com.navercorp.pinpoint.profiler.context.SpanEvent; import com.navercorp.pinpoint.profiler.context.SpanEventFactory; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingContext; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingService; +import com.navercorp.pinpoint.profiler.context.exception.SpanEventException; import com.navercorp.pinpoint.profiler.context.annotation.Annotations; import com.navercorp.pinpoint.profiler.context.errorhandler.IgnoreErrorHandler; import com.navercorp.pinpoint.profiler.context.id.TraceRoot; @@ -42,9 +45,7 @@ import java.util.Objects; /** - * * @author jaehong.kim - * */ public class WrappedSpanEventRecorder extends AbstractRecorder implements SpanEventRecorder { private static final Logger logger = LogManager.getLogger(WrappedSpanEventRecorder.class); @@ -57,31 +58,35 @@ public class WrappedSpanEventRecorder extends AbstractRecorder implements SpanEv private SpanEvent spanEvent; + private ExceptionRecordingContext exceptionRecordingContext; + public WrappedSpanEventRecorder(TraceRoot traceRoot, AsyncContextFactory asyncContextFactory, StringMetaDataService stringMetaDataService, SqlMetaDataService sqlMetaDataService, - IgnoreErrorHandler ignoreErrorHandler) { + IgnoreErrorHandler ignoreErrorHandler, + ExceptionRecordingService exceptionRecordingService) { - this(traceRoot, asyncContextFactory, null, stringMetaDataService, sqlMetaDataService, ignoreErrorHandler); + this(traceRoot, asyncContextFactory, null, stringMetaDataService, sqlMetaDataService, ignoreErrorHandler, exceptionRecordingService); } public WrappedSpanEventRecorder(TraceRoot traceRoot, AsyncContextFactory asyncContextFactory, - @Nullable - final AsyncState asyncState, + @Nullable final AsyncState asyncState, final StringMetaDataService stringMetaDataService, final SqlMetaDataService sqlMetaCacheService, - final IgnoreErrorHandler errorHandler) { - super(stringMetaDataService, sqlMetaCacheService, errorHandler); + final IgnoreErrorHandler errorHandler, + final ExceptionRecordingService exceptionRecordingService) { + super(stringMetaDataService, sqlMetaCacheService, errorHandler, exceptionRecordingService); this.traceRoot = Objects.requireNonNull(traceRoot, "traceRoot"); this.asyncContextFactory = Objects.requireNonNull(asyncContextFactory, "asyncContextFactory"); this.asyncState = asyncState; } - public void setWrapped(final SpanEvent spanEvent) { + public void setWrapped(final SpanEvent spanEvent, ExceptionRecordingContext exceptionRecordingContext) { this.spanEvent = spanEvent; + this.exceptionRecordingContext = exceptionRecordingContext; } @Override @@ -176,6 +181,15 @@ void setExceptionInfo(int exceptionClassId, String exceptionMessage) { this.spanEvent.setExceptionInfo(exceptionClassId, exceptionMessage); } + void recordDetailedException(Throwable throwable) { + SpanEventException spanEventException = exceptionRecordingService.recordException( + this.exceptionRecordingContext, + throwable, + spanEvent.getStartTime() + ); + spanEvent.setFlushedException(spanEventException); + } + @Override public void recordApiId(final int apiId) { setApiId0(apiId); diff --git a/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/DefaultTraceTest.java b/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/DefaultTraceTest.java index d0890bc45bb3a..24d0ac0ca5f0b 100644 --- a/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/DefaultTraceTest.java +++ b/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/DefaultTraceTest.java @@ -21,12 +21,14 @@ import com.navercorp.pinpoint.bootstrap.context.Trace; import com.navercorp.pinpoint.profiler.context.errorhandler.BypassErrorHandler; import com.navercorp.pinpoint.profiler.context.errorhandler.IgnoreErrorHandler; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingContext; import com.navercorp.pinpoint.profiler.context.id.Shared; import com.navercorp.pinpoint.profiler.context.id.TraceRoot; import com.navercorp.pinpoint.profiler.context.recorder.DefaultSpanRecorder; import com.navercorp.pinpoint.profiler.context.recorder.WrappedSpanEventRecorder; import com.navercorp.pinpoint.profiler.context.storage.Storage; import com.navercorp.pinpoint.profiler.logging.Log4j2LoggerBinderInitializer; +import com.navercorp.pinpoint.profiler.context.exception.DefaultExceptionRecordingService; import com.navercorp.pinpoint.profiler.metadata.SqlMetaDataService; import com.navercorp.pinpoint.profiler.metadata.StringMetaDataService; import org.junit.jupiter.api.AfterAll; @@ -59,6 +61,8 @@ public class DefaultTraceTest { private SqlMetaDataService sqlMetaDataService; @Mock private AsyncContextFactory asyncContextFactory; + @Mock + private DefaultExceptionRecordingService exceptionRecordingService; private final IgnoreErrorHandler errorHandler = new BypassErrorHandler(); @@ -156,9 +160,11 @@ private Trace newTrace(final int maxCallStackDepth) { Storage storage = mock(Storage.class); final Span span = spanFactory.newSpan(traceRoot); - final SpanRecorder spanRecorder = new DefaultSpanRecorder(span, stringMetaDataService, sqlMetaDataService, errorHandler); - final WrappedSpanEventRecorder wrappedSpanEventRecorder = new WrappedSpanEventRecorder(traceRoot, asyncContextFactory, stringMetaDataService, sqlMetaDataService, errorHandler); - return new DefaultTrace(span, callStack, storage, spanRecorder, wrappedSpanEventRecorder, CloseListener.EMPTY); + final SpanRecorder spanRecorder = new DefaultSpanRecorder(span, stringMetaDataService, sqlMetaDataService, errorHandler, exceptionRecordingService); + final WrappedSpanEventRecorder wrappedSpanEventRecorder = new WrappedSpanEventRecorder(traceRoot, asyncContextFactory, stringMetaDataService, sqlMetaDataService, errorHandler, exceptionRecordingService); + final ExceptionRecordingContext exceptionRecordingContext = ExceptionRecordingContext.newContext(); + + return new DefaultTrace(span, callStack, storage, spanRecorder, wrappedSpanEventRecorder, exceptionRecordingContext, CloseListener.EMPTY); } } diff --git a/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/TraceTest.java b/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/TraceTest.java index 7ba2ce1ea0b6b..2af3cf4d4e107 100644 --- a/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/TraceTest.java +++ b/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/TraceTest.java @@ -21,6 +21,8 @@ import com.navercorp.pinpoint.bootstrap.context.TraceId; import com.navercorp.pinpoint.profiler.context.errorhandler.BypassErrorHandler; import com.navercorp.pinpoint.profiler.context.errorhandler.IgnoreErrorHandler; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingContext; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingService; import com.navercorp.pinpoint.profiler.context.id.DefaultTraceId; import com.navercorp.pinpoint.profiler.context.id.TraceRoot; import com.navercorp.pinpoint.profiler.context.recorder.DefaultSpanRecorder; @@ -59,6 +61,8 @@ public class TraceTest { private StringMetaDataService stringMetaDataService; @Mock private SqlMetaDataService sqlMetaDataService; + @Mock + private ExceptionRecordingService exceptionRecordingService; private final IgnoreErrorHandler errorHandler = new BypassErrorHandler(); @@ -71,12 +75,13 @@ public void trace() { final CallStack callStack = newCallStack(); final Span span = newSpan(traceRoot); - SpanRecorder spanRecorder = new DefaultSpanRecorder(span, stringMetaDataService, sqlMetaDataService, errorHandler); - WrappedSpanEventRecorder wrappedSpanEventRecorder = new WrappedSpanEventRecorder(traceRoot, asyncContextFactory, stringMetaDataService, sqlMetaDataService, errorHandler); + SpanRecorder spanRecorder = new DefaultSpanRecorder(span, stringMetaDataService, sqlMetaDataService, errorHandler, exceptionRecordingService); + WrappedSpanEventRecorder wrappedSpanEventRecorder = new WrappedSpanEventRecorder(traceRoot, asyncContextFactory, stringMetaDataService, sqlMetaDataService, errorHandler, exceptionRecordingService); + ExceptionRecordingContext exceptionRecordingContext = ExceptionRecordingContext.newContext(); Storage storage = mock(Storage.class); - Trace trace = new DefaultTrace(span, callStack, storage, spanRecorder, wrappedSpanEventRecorder, CloseListener.EMPTY); + Trace trace = new DefaultTrace(span, callStack, storage, spanRecorder, wrappedSpanEventRecorder, exceptionRecordingContext, CloseListener.EMPTY); trace.traceBlockBegin(); // get data form db @@ -101,13 +106,13 @@ public void popEventTest() { final Span span = newSpan(traceRoot); - SpanRecorder spanRecorder = new DefaultSpanRecorder(span, stringMetaDataService, sqlMetaDataService, errorHandler); - WrappedSpanEventRecorder wrappedSpanEventRecorder = new WrappedSpanEventRecorder(traceRoot, asyncContextFactory, stringMetaDataService, sqlMetaDataService, errorHandler); - + SpanRecorder spanRecorder = new DefaultSpanRecorder(span, stringMetaDataService, sqlMetaDataService, errorHandler, exceptionRecordingService); + WrappedSpanEventRecorder wrappedSpanEventRecorder = new WrappedSpanEventRecorder(traceRoot, asyncContextFactory, stringMetaDataService, sqlMetaDataService, errorHandler, exceptionRecordingService); + ExceptionRecordingContext exceptionRecordingContext = ExceptionRecordingContext.newContext(); Storage storage = mock(Storage.class); - Trace trace = new DefaultTrace(span, callStack, storage, spanRecorder, wrappedSpanEventRecorder, CloseListener.EMPTY); + Trace trace = new DefaultTrace(span, callStack, storage, spanRecorder, wrappedSpanEventRecorder, exceptionRecordingContext, CloseListener.EMPTY); trace.close(); diff --git a/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/recorder/DefaultSpanRecorderTest.java b/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/recorder/DefaultSpanRecorderTest.java index 07b3f70e9810e..1844e5c400b4e 100644 --- a/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/recorder/DefaultSpanRecorderTest.java +++ b/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/recorder/DefaultSpanRecorderTest.java @@ -20,6 +20,7 @@ import com.navercorp.pinpoint.profiler.context.Span; import com.navercorp.pinpoint.profiler.context.errorhandler.BypassErrorHandler; import com.navercorp.pinpoint.profiler.context.errorhandler.IgnoreErrorHandler; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingService; import com.navercorp.pinpoint.profiler.context.id.Shared; import com.navercorp.pinpoint.profiler.context.id.TraceRoot; import com.navercorp.pinpoint.profiler.metadata.SqlMetaDataService; @@ -48,6 +49,8 @@ public class DefaultSpanRecorderTest { private StringMetaDataService stringMetaDataService; @Mock private SqlMetaDataService sqlMetaDataService; + @Mock + private ExceptionRecordingService exceptionRecordingService; private final IgnoreErrorHandler errorHandler = new BypassErrorHandler(); @@ -55,7 +58,7 @@ public class DefaultSpanRecorderTest { public void testRecordApiId() { Span span = new Span(traceRoot); - SpanRecorder recorder = new DefaultSpanRecorder(span, stringMetaDataService, sqlMetaDataService, errorHandler); + SpanRecorder recorder = new DefaultSpanRecorder(span, stringMetaDataService, sqlMetaDataService, errorHandler, exceptionRecordingService); final int API_ID = 1000; recorder.recordApiId(API_ID); @@ -70,7 +73,7 @@ public void testRecordEndPoint() { Span span = new Span(traceRoot); - SpanRecorder recorder = new DefaultSpanRecorder(span, stringMetaDataService, sqlMetaDataService, errorHandler); + SpanRecorder recorder = new DefaultSpanRecorder(span, stringMetaDataService, sqlMetaDataService, errorHandler, exceptionRecordingService); final String endPoint = "endPoint"; recorder.recordEndPoint(endPoint); diff --git a/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/recorder/WrappedSpanEventRecorderTest.java b/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/recorder/WrappedSpanEventRecorderTest.java index da46ea27466c5..c70a66fbfc064 100644 --- a/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/recorder/WrappedSpanEventRecorderTest.java +++ b/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/recorder/WrappedSpanEventRecorderTest.java @@ -20,6 +20,8 @@ import com.navercorp.pinpoint.profiler.context.SpanEvent; import com.navercorp.pinpoint.profiler.context.errorhandler.BypassErrorHandler; import com.navercorp.pinpoint.profiler.context.errorhandler.IgnoreErrorHandler; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingContext; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingService; import com.navercorp.pinpoint.profiler.context.id.Shared; import com.navercorp.pinpoint.profiler.context.id.TraceRoot; import com.navercorp.pinpoint.profiler.metadata.SqlMetaDataService; @@ -58,6 +60,9 @@ public class WrappedSpanEventRecorderTest { @Mock private SqlMetaDataService sqlMetaDataService; + @Mock + private ExceptionRecordingService exceptionRecordingService; + private final IgnoreErrorHandler errorHandler = new BypassErrorHandler(); @Test @@ -65,8 +70,9 @@ public void testSetExceptionInfo_RootMarkError() throws Exception { when(traceRoot.getShared()).thenReturn(shared); SpanEvent spanEvent = new SpanEvent(); - WrappedSpanEventRecorder recorder = new WrappedSpanEventRecorder(traceRoot, asyncContextFactory, stringMetaDataService, sqlMetaDataService, errorHandler); - recorder.setWrapped(spanEvent); + WrappedSpanEventRecorder recorder = new WrappedSpanEventRecorder(traceRoot, asyncContextFactory, stringMetaDataService, sqlMetaDataService, errorHandler, exceptionRecordingService); + ExceptionRecordingContext exceptionRecordingContext = ExceptionRecordingContext.newContext(); + recorder.setWrapped(spanEvent, exceptionRecordingContext); final String exceptionMessage1 = "exceptionMessage1"; final Exception exception1 = new Exception(exceptionMessage1); @@ -87,8 +93,9 @@ public void testSetExceptionInfo_RootMarkError() throws Exception { @Test public void testRecordAPIId() throws Exception { SpanEvent spanEvent = new SpanEvent(); - WrappedSpanEventRecorder recorder = new WrappedSpanEventRecorder(traceRoot, asyncContextFactory, stringMetaDataService, sqlMetaDataService, errorHandler); - recorder.setWrapped(spanEvent); + WrappedSpanEventRecorder recorder = new WrappedSpanEventRecorder(traceRoot, asyncContextFactory, stringMetaDataService, sqlMetaDataService, errorHandler, exceptionRecordingService); + ExceptionRecordingContext exceptionRecordingContext = ExceptionRecordingContext.newContext(); + recorder.setWrapped(spanEvent, exceptionRecordingContext); final int API_ID = 1000; diff --git a/profiler/src/test/java/com/navercorp/pinpoint/profiler/metadata/DefaultExceptionRecordingServiceTest.java b/profiler/src/test/java/com/navercorp/pinpoint/profiler/metadata/DefaultExceptionRecordingServiceTest.java new file mode 100644 index 0000000000000..a4e14d622d5cc --- /dev/null +++ b/profiler/src/test/java/com/navercorp/pinpoint/profiler/metadata/DefaultExceptionRecordingServiceTest.java @@ -0,0 +1,171 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.profiler.metadata; + +import com.navercorp.pinpoint.profiler.context.DefaultReference; +import com.navercorp.pinpoint.profiler.context.Reference; +import com.navercorp.pinpoint.profiler.context.exception.AtomicExceptionIdGenerator; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionIdGenerator; +import com.navercorp.pinpoint.profiler.context.exception.ExceptionRecordingContext; +import com.navercorp.pinpoint.profiler.context.exception.DefaultExceptionRecordingService; +import com.navercorp.pinpoint.profiler.context.exception.SpanEventException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.function.Function; + +/** + * @author intr3p1d + */ +public class DefaultExceptionRecordingServiceTest { + + private final static Logger logger = LogManager.getLogger(DefaultExceptionRecordingServiceTest.class); + + ExceptionIdGenerator exceptionIdGenerator = new AtomicExceptionIdGenerator(); + DefaultExceptionRecordingService exceptionRecordingService = new DefaultExceptionRecordingService(exceptionIdGenerator); + + ExceptionRecordingContext context; + + long START_TIME = 1; + + private Function throwableInterceptor(Reference throwableReference) { + return (Throwable throwable) -> { + throwableReference.set(throwable); + exceptionRecordingService.recordException(context, throwable, START_TIME); + logger.info(throwable); + return throwable; + }; + } + + public void methodA(Function interceptor) throws Throwable { + throw interceptor.apply(new RuntimeException("Level 1 Error")); + } + + public void methodB(Function interceptor) throws Throwable { + try { + methodA(interceptor); + } catch (Throwable e) { + throw interceptor.apply(new RuntimeException("Level 2 Error", e)); + } + } + + public void methodC(Function interceptor) throws Throwable { + try { + methodB(interceptor); + } catch (Throwable e) { + throw interceptor.apply(new RuntimeException("Level 3 Error", e)); + } + } + + public void resetContext() { + context = ExceptionRecordingContext.newContext(); + } + + @Test + public void testRecordException() { + resetContext(); + SpanEventException actual = null; + SpanEventException expected = null; + + Reference reference = new DefaultReference<>(); + Function throwableInterceptor = throwableInterceptor(reference); + + try { + methodC(throwableInterceptor); + } catch (Throwable e) { + actual = exceptionRecordingService.recordException(context, e, START_TIME); + expected = SpanEventException.newSpanEventException(e, START_TIME, exceptionIdGenerator.getCurrentExceptionId()); + Assertions.assertNull(actual); + } + actual = exceptionRecordingService.recordException(context, null, 0); + Assertions.assertEquals(actual, expected); + } + + @Test + public void testRecordNothing() { + resetContext(); + SpanEventException actual = null; + SpanEventException expected = null; + actual = exceptionRecordingService.recordException(context, null, 0); + Assertions.assertEquals(actual, expected); + } + + + public void notChainedException(Function interceptor) throws Throwable { + try { + methodC(interceptor); + } catch (Throwable e) { + throw interceptor.apply(new RuntimeException("Not Chained, Another New Exception")); + } + } + + @Test + public void testRecordNotChainedException() { + resetContext(); + SpanEventException actual = null; + SpanEventException expected1 = null; + SpanEventException expected2 = null; + + Reference reference = new DefaultReference<>(); + Function throwableInterceptor = throwableInterceptor(reference); + Throwable throwable = null; + + try { + notChainedException(throwableInterceptor); + } catch (Throwable e) { + actual = exceptionRecordingService.recordException(context, e, START_TIME); + expected1 = SpanEventException.newSpanEventException(reference.get(), START_TIME, exceptionIdGenerator.getCurrentExceptionId()); + throwable = e; + Assertions.assertNotNull(actual); + Assertions.assertEquals(actual, expected1); + } + + actual = exceptionRecordingService.recordException(context, null, 0); + expected2 = SpanEventException.newSpanEventException(throwable, START_TIME, exceptionIdGenerator.getCurrentExceptionId()); + Assertions.assertEquals(actual, expected2); + } + + public void rethrowGivenException(Function interceptor) throws Throwable { + try { + methodC(interceptor); + } catch (Exception e) { + throw interceptor.apply(e); + } + } + + @Test + public void testRecordRethrowGivenException() { + resetContext(); + SpanEventException actual = null; + SpanEventException expected = null; + + Reference reference = new DefaultReference<>(); + Function throwableInterceptor = throwableInterceptor(reference); + + try { + rethrowGivenException(throwableInterceptor); + } catch (Throwable e) { + actual = exceptionRecordingService.recordException(context, e, START_TIME); + expected = SpanEventException.newSpanEventException(e, START_TIME, exceptionIdGenerator.getCurrentExceptionId()); + Assertions.assertNull(actual); + } + + actual = exceptionRecordingService.recordException(context, null, 0); + Assertions.assertEquals(actual, expected); + } +}