From f960e43f8d2c9fd6814cc22f984fa6bd15bc3948 Mon Sep 17 00:00:00 2001 From: musketyr Date: Thu, 5 May 2022 17:00:48 +0200 Subject: [PATCH 1/2] ensure scope nested properly --- .../micronaut/log4aws/http/SentryFilter.java | 19 +- .../log4aws/http/GlobalErrorHandler.groovy | 26 ++ .../micronaut/log4aws/http/MockHub.java | 243 ++++++++++++++++++ .../log4aws/http/SentryFilterSpec.groovy | 79 ++---- 4 files changed, 306 insertions(+), 61 deletions(-) create mode 100644 subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/GlobalErrorHandler.groovy create mode 100644 subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/MockHub.java diff --git a/subprojects/micronaut-log4aws/src/main/java/com/agorapulse/micronaut/log4aws/http/SentryFilter.java b/subprojects/micronaut-log4aws/src/main/java/com/agorapulse/micronaut/log4aws/http/SentryFilter.java index b7a135d..9fe4fe1 100644 --- a/subprojects/micronaut-log4aws/src/main/java/com/agorapulse/micronaut/log4aws/http/SentryFilter.java +++ b/subprojects/micronaut-log4aws/src/main/java/com/agorapulse/micronaut/log4aws/http/SentryFilter.java @@ -28,6 +28,8 @@ import io.sentry.IHub; import org.reactivestreams.Publisher; +import java.util.concurrent.atomic.AtomicReference; + @Filter("/**") public class SentryFilter implements HttpServerFilter { @@ -46,14 +48,15 @@ public int getOrder() { public Publisher> doFilter(HttpRequest request, ServerFilterChain chain) { return Flowable .just(request) - .doOnNext(r -> { - hub.pushScope(); - hub.addBreadcrumb(Breadcrumb.http(request.getUri().toString(), request.getMethodName())); - hub.configureScope(scope -> scope.addEventProcessor(new MicronautRequestEventProcessor(request))); - }) - .switchMap(chain::proceed) - .doOnError(throwable -> hub.popScope()) - .doOnNext(res -> hub.popScope()); + .switchMap(r -> { + AtomicReference>> responsePublisher = new AtomicReference<>(); + hub.withScope(scope -> { + scope.addEventProcessor(new MicronautRequestEventProcessor(request)); + scope.addBreadcrumb(Breadcrumb.http(request.getUri().toString(), request.getMethodName())); + responsePublisher.set(chain.proceed(r)); + }); + return responsePublisher.get(); + }); } } diff --git a/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/GlobalErrorHandler.groovy b/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/GlobalErrorHandler.groovy new file mode 100644 index 0000000..070c24b --- /dev/null +++ b/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/GlobalErrorHandler.groovy @@ -0,0 +1,26 @@ +package com.agorapulse.micronaut.log4aws.http + +import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j +import io.micronaut.http.HttpResponse +import io.micronaut.http.annotation.Controller +import io.micronaut.http.annotation.Error +import io.micronaut.http.hateoas.JsonError +import io.micronaut.web.router.exceptions.UnsatisfiedRouteException + +@Controller +@Slf4j +@CompileStatic +class GlobalErrorHandler { + + // Default handler, will not be used if the exception is handled by another specific one + @Error(global = true, exception = Exception) + HttpResponse serverError(Exception e) { + log.error(e.message, e) + if (e instanceof UnsatisfiedRouteException) { + return HttpResponse.badRequest(new JsonError(e.message)) + } + return HttpResponse.serverError().body(new JsonError(e.message)) + } + +} diff --git a/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/MockHub.java b/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/MockHub.java new file mode 100644 index 0000000..992cc5e --- /dev/null +++ b/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/MockHub.java @@ -0,0 +1,243 @@ +package com.agorapulse.micronaut.log4aws.http; + +import io.sentry.*; +import io.sentry.exception.InvalidSentryTraceHeaderException; +import io.sentry.protocol.SentryId; +import io.sentry.protocol.SentryTransaction; +import io.sentry.protocol.User; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class MockHub implements IHub { + + private final List scopes = new ArrayList<>(); + private int pointer = -1; + + public List getScopes() { + return scopes; + } + + public int getPointer() { + return pointer; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public @NotNull SentryId captureEvent(@NotNull SentryEvent event, @Nullable Object hint) { + System.err.printf("Got an event %s with hint %s when scope is %s%n", event, hint, getCurrentScope()); + return new SentryId(); + } + + @Override + public @NotNull SentryId captureMessage(@NotNull String message, @NotNull SentryLevel level) { + System.err.printf("Got a message %s with level %s when scope is %s%n", message, level, getCurrentScope()); + return new SentryId(); + } + + @Override + public @NotNull SentryId captureEnvelope(@NotNull SentryEnvelope envelope, @Nullable Object hint) { + System.err.printf("Got an envelope %s with hint %s when scope is %s%n", envelope, hint, getCurrentScope()); + return new SentryId(); + } + + @Override + public @NotNull SentryId captureException(@NotNull Throwable throwable, @Nullable Object hint) { + System.err.printf("Got an envelope %s with hint %s when scope is %s%n", throwable, hint, getCurrentScope()); + return new SentryId(); + } + + @Override + public void captureUserFeedback(@NotNull UserFeedback userFeedback) { + System.err.printf("Got a feedback %s when scope is %s%n", userFeedback, getCurrentScope()); + } + + @Override + public void startSession() { + System.err.printf("Starting the new session"); + } + + @Override + public void endSession() { + System.err.printf("Ending the current session"); + } + + @Override + public void close() { + System.err.printf("Closing the hub"); + } + + @Override + public void addBreadcrumb(@NotNull Breadcrumb breadcrumb, @Nullable Object hint) { + System.err.printf("Got breadcrumb %s with hint %s when scope is %s%n", breadcrumb, hint, getCurrentScope()); + getCurrentScope().addBreadcrumb(breadcrumb, hint); + } + + @Override + public void setLevel(@Nullable SentryLevel level) { + System.err.printf("New level is " + level); + getCurrentScope().setLevel(level); + } + + @Override + public void setTransaction(@Nullable String transaction) { + System.err.printf("New transaction is " + transaction); + getCurrentScope().setTransaction(transaction); + } + + @Override + public void setUser(@Nullable User user) { + System.err.printf("New user is " + user); + getCurrentScope().setUser(user); + } + + @Override + public void setFingerprint(@NotNull List fingerprint) { + System.err.printf("New fingerprint is " + fingerprint); + getCurrentScope().setFingerprint(fingerprint); + } + + @Override + public void clearBreadcrumbs() { + System.err.printf("Clearing breadcrumbs"); + getCurrentScope().clearBreadcrumbs(); + } + + @Override + public void setTag(@NotNull String key, @NotNull String value) { + System.err.printf("Setting tag with key %s and value %s when scope is %s%n", key, value, getCurrentScope()); + getCurrentScope().setTag(key, value); + } + + @Override + public void removeTag(@NotNull String key) { + System.err.printf("Removing tag with key %s when scope is %s%n", key, getCurrentScope()); + getCurrentScope().removeTag(key); + } + + @Override + public void setExtra(@NotNull String key, @NotNull String value) { + System.err.printf("Setting extra with key %s and value %s when scope is %s%n", key, value, getCurrentScope()); + getCurrentScope().setExtra(key, value); + } + + @Override + public void removeExtra(@NotNull String key) { + System.err.printf("Removing extra with key %s when scope is %s%n", key, getCurrentScope()); + getCurrentScope().removeExtra(key); + } + + @Override + public @NotNull SentryId getLastEventId() { + return new SentryId(); + } + + @Override + public void pushScope() { + pointer++; + scopes.add(pointer, new Scope(getOptions())); + System.err.printf("Pushed scope %s%n", getCurrentScope()); + } + + @Override + public void popScope() { + System.err.printf("Popping scope %s%n", getCurrentScope()); + pointer--; + } + + @Override + public void withScope(@NotNull ScopeCallback callback) { + pushScope(); + configureScope(callback); + popScope(); + } + + @Override + public void configureScope(@NotNull ScopeCallback callback) { + System.err.printf("Configuring scope %s%n", getCurrentScope()); + callback.run(getCurrentScope()); + } + + @Override + public void bindClient(@NotNull ISentryClient client) { + System.err.printf("Binding client %s when scope is %s%n", client, getCurrentScope()); + } + + @Override + public void flush(long timeoutMillis) { + System.err.printf("Flushing with timeout %s when scope is %s%n", timeoutMillis, getCurrentScope()); + } + + + //CHECKSTYLE:OFF + @Override + public @NotNull IHub clone() { + return this; + } + //CHECKSTYLE:ON + + @Override + public @NotNull SentryId captureTransaction(@NotNull SentryTransaction transaction, @Nullable TraceState traceState, @Nullable Object hint) { + System.err.printf("Capturing transaction %s with state %s and hint %s when scope is %s%n", transaction, traceState, hint, getCurrentScope()); + return new SentryId(); + } + + @Override + public @NotNull ITransaction startTransaction(@NotNull TransactionContext transactionContexts, @Nullable CustomSamplingContext customSamplingContext, boolean bindToScope) { + System.err.printf("Capturing transaction with contexts %s with sampling context %s and bind to scope %s when scope is %s%n", transactionContexts, customSamplingContext, bindToScope, getCurrentScope()); + return getCurrentScope().getTransaction(); + } + + @Override + public @NotNull ITransaction startTransaction(@NotNull TransactionContext transactionContexts, @Nullable CustomSamplingContext customSamplingContext, boolean bindToScope, @Nullable Date startTimestamp) { + System.err.printf("Capturing transaction with contexts %s with sampling context %s and bind to scope %s and start timestamp %s when scope is %s%n", transactionContexts, customSamplingContext, bindToScope, startTimestamp, getCurrentScope()); + return getCurrentScope().getTransaction(); + } + + @Override + public @NotNull ITransaction startTransaction(@NotNull TransactionContext transactionContexts, @Nullable CustomSamplingContext customSamplingContext, boolean bindToScope, @Nullable Date startTimestamp, boolean waitForChildren, @Nullable TransactionFinishedCallback transactionFinishedCallback) { + System.err.printf("Capturing transaction with contexts %s with sampling context %s and bind to scope %s and start timestamp %s and wait for children %s and callback %s when scope is %s%n", transactionContexts, customSamplingContext, bindToScope, startTimestamp, waitForChildren, transactionContexts, getCurrentScope()); + return getCurrentScope().getTransaction(); + } + + @Override + public @Nullable SentryTraceHeader traceHeaders() { + try { + return new SentryTraceHeader("traceid-spanid"); + } catch (InvalidSentryTraceHeaderException e) { + throw new RuntimeException(e); + } + } + + @Override + public void setSpanContext(@NotNull Throwable throwable, @NotNull ISpan span, @NotNull String transactionName) { + System.err.printf("Setting span context %s to span %s with transaction name %s when scope is %s%n", throwable, span, transactionName, getCurrentScope()); + } + + @Override + public @Nullable ISpan getSpan() { + return NoOpSpan.getInstance(); + } + + @Override + public @NotNull SentryOptions getOptions() { + return new SentryOptions(); + } + + @Override + public @Nullable Boolean isCrashedLastRun() { + return false; + } + + private Scope getCurrentScope() { + if (pointer < 0) { + return null; + } + return scopes.get(pointer); + } +} diff --git a/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/SentryFilterSpec.groovy b/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/SentryFilterSpec.groovy index 1922f6c..8ed5c46 100644 --- a/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/SentryFilterSpec.groovy +++ b/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/SentryFilterSpec.groovy @@ -22,11 +22,10 @@ import com.agorapulse.gru.http.Http import io.micronaut.context.ApplicationContext import io.micronaut.runtime.server.EmbeddedServer import io.sentry.IHub +import io.sentry.Scope import spock.lang.AutoCleanup import spock.lang.Specification -import java.util.concurrent.atomic.AtomicInteger - class SentryFilterSpec extends Specification { @AutoCleanup Gru gru = Gru.create(Http.create(this)) @@ -34,7 +33,7 @@ class SentryFilterSpec extends Specification { @AutoCleanup ApplicationContext context @AutoCleanup EmbeddedServer server - IHub hub = Mock() + IHub hub = new MockHub() void setup() { context = ApplicationContext.builder().build() @@ -59,18 +58,17 @@ class SentryFilterSpec extends Specification { then: gru.verify() - 1 * hub.pushScope() - 1 * hub.addBreadcrumb(_) - 1 * hub.configureScope(_) - 1 * hub.popScope() + hub.pointer == -1 + hub.scopes.size() == 1 + + when: + Scope scope = hub.scopes[0] + then: + scope.breadcrumbs.size() == 1 + scope.eventProcessors.size() == 1 } void 'try error message'() { - given: - AtomicInteger pushCalls = new AtomicInteger() - AtomicInteger breadcrumbsCalls = new AtomicInteger() - AtomicInteger configureScopeCalls = new AtomicInteger() - AtomicInteger popCalls = new AtomicInteger() when: gru.test { post('/test/parameter') @@ -81,33 +79,18 @@ class SentryFilterSpec extends Specification { then: gru.verify() - _ * hub.pushScope() >> { - pushCalls.incrementAndGet() - } - _ * hub.addBreadcrumb(_) >> { - breadcrumbsCalls.incrementAndGet() - } - _ * hub.configureScope(_) >> { - configureScopeCalls.incrementAndGet() - } - _ * hub.popScope() >> { - popCalls.incrementAndGet() - } - - expect: + hub.pointer == -1 // the filter is only called once for Micronaut 3.x but twice for Micronaut 1.x and 2.x - pushCalls.get() in 1..2 - pushCalls.get() == breadcrumbsCalls.get() - pushCalls.get() == configureScopeCalls.get() - pushCalls.get() == popCalls.get() + hub.scopes.size() in 1..2 + + when: + Scope scope = hub.scopes[0] + then: + scope.breadcrumbs.size() in 1 + scope.eventProcessors.size() == 1 } void 'try validation'() { - given: - AtomicInteger pushCalls = new AtomicInteger() - AtomicInteger breadcrumbsCalls = new AtomicInteger() - AtomicInteger configureScopeCalls = new AtomicInteger() - AtomicInteger popCalls = new AtomicInteger() when: gru.test { put('/test/validated') @@ -118,25 +101,15 @@ class SentryFilterSpec extends Specification { then: gru.verify() - _ * hub.pushScope() >> { - pushCalls.incrementAndGet() - } - _ * hub.addBreadcrumb(_) >> { - breadcrumbsCalls.incrementAndGet() - } - _ * hub.configureScope(_) >> { - configureScopeCalls.incrementAndGet() - } - _ * hub.popScope() >> { - popCalls.incrementAndGet() - } - - expect: + hub.pointer == -1 // the filter is only called once for Micronaut 3.x but twice for Micronaut 1.x and 2.x - pushCalls.get() in 1..2 - pushCalls.get() == breadcrumbsCalls.get() - pushCalls.get() == configureScopeCalls.get() - pushCalls.get() == popCalls.get() + hub.scopes.size() in 1..2 + + when: + Scope scope = hub.scopes[0] + then: + scope.breadcrumbs.size() in 1 + scope.eventProcessors.size() == 1 } } From cc7885f1b26edb8bf44aecab4e8417bdf9e643c7 Mon Sep 17 00:00:00 2001 From: musketyr Date: Thu, 5 May 2022 17:04:33 +0200 Subject: [PATCH 2/2] license headers --- .../log4aws/http/GlobalErrorHandler.groovy | 17 +++++++++++++++++ .../micronaut/log4aws/http/MockHub.java | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/GlobalErrorHandler.groovy b/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/GlobalErrorHandler.groovy index 070c24b..b89f162 100644 --- a/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/GlobalErrorHandler.groovy +++ b/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/GlobalErrorHandler.groovy @@ -1,3 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2020-2022 Agorapulse. + * + * 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 + * + * https://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.agorapulse.micronaut.log4aws.http import groovy.transform.CompileStatic diff --git a/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/MockHub.java b/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/MockHub.java index 992cc5e..e20c3b3 100644 --- a/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/MockHub.java +++ b/subprojects/micronaut-log4aws/src/test/groovy/com/agorapulse/micronaut/log4aws/http/MockHub.java @@ -1,3 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2020-2022 Agorapulse. + * + * 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 + * + * https://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.agorapulse.micronaut.log4aws.http; import io.sentry.*;