From 93a657a0a28c75b7ba2131708805d6677fb9c7a4 Mon Sep 17 00:00:00 2001 From: Vladimir Orany Date: Thu, 22 Oct 2020 14:20:00 +0200 Subject: [PATCH 1/2] rewritten to v3 and added support for HTTP --- .../log4aws/AwsLambdaEventBuildHelper.java | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/AwsLambdaEventBuildHelper.java b/subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/AwsLambdaEventBuildHelper.java index 88dd909..c4ae1b0 100644 --- a/subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/AwsLambdaEventBuildHelper.java +++ b/subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/AwsLambdaEventBuildHelper.java @@ -17,22 +17,25 @@ */ package com.agorapulse.micronaut.log4aws; -import io.sentry.event.EventBuilder; -import io.sentry.event.helper.EventBuilderHelper; +import io.micronaut.context.annotation.Context; +import io.sentry.EventProcessor; +import io.sentry.SentryEvent; -public class AwsLambdaEventBuildHelper implements EventBuilderHelper { +@Context +public class AwsLambdaEventBuildHelper implements EventProcessor { @Override - public void helpBuildingEvent(EventBuilder eventBuilder) { - eventBuilder.withTag("aws_region", System.getenv("AWS_REGION")); - eventBuilder.withTag("aws_default_region", System.getenv("AWS_DEFAULT_REGION")); - eventBuilder.withTag("lambda_function_name", System.getenv("AWS_LAMBDA_FUNCTION_NAME")); - eventBuilder.withTag("lambda_function_version", System.getenv("AWS_LAMBDA_FUNCTION_VERSION")); - eventBuilder.withTag("lambda_handler", System.getenv("_HANDLER")); - eventBuilder.withTag("lambda_execution_environment", System.getenv("AWS_EXECUTION_ENV")); - eventBuilder.withTag("lambda_log_group_name", System.getenv("AWS_LAMBDA_LOG_GROUP_NAME")); - eventBuilder.withTag("lambda_log_stream_name", System.getenv("AWS_LAMBDA_LOG_STREAM_NAME")); - eventBuilder.withTag("lambda_memory_size", System.getenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE")); + public SentryEvent process(SentryEvent event, Object hint) { + event.setTag("aws_region", System.getenv("AWS_REGION")); + event.setTag("aws_default_region", System.getenv("AWS_DEFAULT_REGION")); + event.setTag("lambda_function_name", System.getenv("AWS_LAMBDA_FUNCTION_NAME")); + event.setTag("lambda_function_version", System.getenv("AWS_LAMBDA_FUNCTION_VERSION")); + event.setTag("lambda_handler", System.getenv("_HANDLER")); + event.setTag("lambda_execution_environment", System.getenv("AWS_EXECUTION_ENV")); + event.setTag("lambda_log_group_name", System.getenv("AWS_LAMBDA_LOG_GROUP_NAME")); + event.setTag("lambda_log_stream_name", System.getenv("AWS_LAMBDA_LOG_STREAM_NAME")); + event.setTag("lambda_memory_size", System.getenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE")); + return event; } } From d7670c2a246602fbd4a2fc2bf6e6cfb815ad7d39 Mon Sep 17 00:00:00 2001 From: Vladimir Orany Date: Thu, 22 Oct 2020 14:22:22 +0200 Subject: [PATCH 2/2] rewritten to v3 and added support for HTTP --- gradle.properties | 2 +- .../micronaut-log4aws.gradle | 3 + .../micronaut/log4aws/Log4AwsFactory.java | 122 +++++++----------- .../MicronautHttpRequestEventProcessor.java | 76 +++++++++++ .../micronaut/log4aws/http/SentryFilter.java | 54 ++++++++ .../log4aws/test/Log4AwsFactorySpec.groovy | 4 +- 6 files changed, 183 insertions(+), 78 deletions(-) create mode 100644 subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/http/MicronautHttpRequestEventProcessor.java create mode 100644 subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/http/SentryFilter.java diff --git a/gradle.properties b/gradle.properties index a47deae..bfb5cf4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,7 +24,7 @@ coverallsPluginVersion = 2.9.0 gitPublishPluginVersion = 2.1.3 micronautVersion = 1.2.8 -sentryVersion = 1.7.30 +sentryVersion = 3.1.1 log4jVersion = 2.13.3 awsLog4jVersion = 1.2.0 systemRulesVersion = 1.19.0 diff --git a/subprojects/micronaut-log4aws/micronaut-log4aws.gradle b/subprojects/micronaut-log4aws/micronaut-log4aws.gradle index 97aa8e0..dc67742 100644 --- a/subprojects/micronaut-log4aws/micronaut-log4aws.gradle +++ b/subprojects/micronaut-log4aws/micronaut-log4aws.gradle @@ -36,6 +36,9 @@ dependencies { compile "io.micronaut:micronaut-core" compileOnly "io.micronaut:micronaut-inject-groovy" + compileOnly 'io.micronaut:micronaut-http' + compileOnly 'io.reactivex.rxjava2:rxjava' + compileOnly "io.sentry:sentry-servlet:$sentryVersion" testAnnotationProcessor "io.micronaut:micronaut-inject-java" diff --git a/subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/Log4AwsFactory.java b/subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/Log4AwsFactory.java index 3e5be4c..d81838b 100644 --- a/subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/Log4AwsFactory.java +++ b/subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/Log4AwsFactory.java @@ -20,12 +20,8 @@ import io.micronaut.context.annotation.Bean; import io.micronaut.context.annotation.Context; import io.micronaut.context.annotation.Factory; -import io.sentry.Sentry; -import io.sentry.SentryClient; -import io.sentry.config.Lookup; -import io.sentry.connection.EventSendCallback; -import io.sentry.dsn.Dsn; -import io.sentry.event.Event; +import io.sentry.*; +import io.sentry.config.PropertiesProviderFactory; import io.sentry.log4j2.SentryAppender; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; @@ -33,13 +29,10 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.util.List; import java.util.Map; -import java.util.function.Supplier; - -import static io.sentry.DefaultSentryClientFactory.ASYNC_OPTION; +import java.util.Optional; /** * Initializes Sentry for Micronaut. @@ -47,92 +40,45 @@ @Factory public class Log4AwsFactory { - private static final Logger LOGGER = LoggerFactory.getLogger(Log4AwsFactory.class); - /** * Automatically registers sentry logging during application context startup. - * @return sentry appender - */ - @Bean - @Context - public SentryAppender sentryAppender() { - boolean sync = !Boolean.FALSE.toString().equals(Lookup.getDefault().get(ASYNC_OPTION)); - boolean dsnProvided = !Dsn.DEFAULT_DSN.equals(Dsn.dsnLookup()); - - if (sync && dsnProvided) { - // in future releases - // throw new IllegalStateException("Sentry not configured correctly for synchornous calls! Please, create file 'sentry.properties' and add there a line 'async=false'"); - LOGGER.error("Sentry not configured correctly for synchornous calls! Please, create file 'sentry.properties' and add there a line 'async=false'"); - } - - return initializeAppenderIfMissing( - SentryAppender.class, - Level.WARN, - SentryAppender.APPENDER_NAME, - SentryAppender::new - ); - } - - /** - * Sentry client to be injected. * - * Please, use the injection instead of static reference to simplify testing. - * - * @return sentry client + * @return sentry appender */ @Bean @Context - public SentryClient sentryClient() { - Sentry.init(); - - SentryClient client = Sentry.getStoredClient(); - client.addBuilderHelper(new AwsLambdaEventBuildHelper()); - client.addEventSendCallback(new EventSendCallback() { - @Override - public void onFailure(Event event, Exception exception) { - LOGGER.error("Failed to send event to Sentry: " + event, exception); - } - - @Override - public void onSuccess(Event event) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Event sent to Sentry: " + event); - } - } - }); - - return client; - } - - private A initializeAppenderIfMissing( - Class appenderClass, - Level defaultLevel, - String defaultName, - Supplier initializer - ) { + public SentryAppender sentryAppender(IHub hub) { LoggerContext lc = (LoggerContext) LogManager.getContext(false); Configuration configuration = lc.getConfiguration(); - String name = defaultName; - A appender = null; + String name = "Sentry"; + SentryAppender appender = null; for (Map.Entry e : configuration.getAppenders().entrySet()) { - if (appenderClass.isInstance(e.getValue())) { - appender = appenderClass.cast(e.getValue()); + if (e.getValue() instanceof SentryAppender) { + appender = (SentryAppender) e.getValue(); name = e.getKey(); } } if (appender == null) { - appender = initializer.get(); + SentryOptions options = SentryOptions.from(PropertiesProviderFactory.create()); + appender = new SentryAppender( + name, + null, + Optional.ofNullable(options.getDsn()).orElse(""), + Level.INFO, + Level.ERROR, + options.getTransport(), + hub); appender.start(); configuration.addAppender(appender); } LoggerConfig rootLoggerConfig = configuration.getRootLogger(); - if (rootLoggerConfig.getAppenders().values().stream().noneMatch(appenderClass::isInstance)) { + if (rootLoggerConfig.getAppenders().values().stream().noneMatch(SentryAppender.class::isInstance)) { rootLoggerConfig.removeAppender(name); - rootLoggerConfig.addAppender(configuration.getAppender(name), defaultLevel, null); + rootLoggerConfig.addAppender(configuration.getAppender(name), Level.WARN, null); } lc.updateLoggers(); @@ -140,4 +86,30 @@ private A initializeAppenderIfMissing( return appender; } + /** + * Sentry client to be injected. + *

+ * Please, use the injection instead of static reference to simplify testing. + * + * @return sentry client + */ + @Bean(preDestroy = "close") + @Context + public IHub sentryHub( + List eventProcessors, + List integrations, + List scopeObservers + ) { + SentryOptions propertiesOptions = SentryOptions.from(PropertiesProviderFactory.create()); + + Optional.ofNullable(propertiesOptions.getDsn()).ifPresent(dsn -> Sentry.init(options -> { + options.setEnableExternalConfiguration(true); + options.setDsn(dsn); + eventProcessors.forEach(options::addEventProcessor); + integrations.forEach(options::addIntegration); + scopeObservers.forEach(options::addScopeObserver); + })); + + return HubAdapter.getInstance(); + } } diff --git a/subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/http/MicronautHttpRequestEventProcessor.java b/subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/http/MicronautHttpRequestEventProcessor.java new file mode 100644 index 0000000..dfdc968 --- /dev/null +++ b/subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/http/MicronautHttpRequestEventProcessor.java @@ -0,0 +1,76 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2020 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.micronaut.http.HttpRequest; +import io.sentry.EventProcessor; +import io.sentry.SentryEvent; +import io.sentry.protocol.Request; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class MicronautHttpRequestEventProcessor implements EventProcessor { + + private static final List SENSITIVE_HEADERS = + Arrays.asList("X-FORWARDED-FOR", "AUTHORIZATION", "COOKIE"); + + private static final List SENSITIVE_PARAMS = + Arrays.asList("TOKEN"); + + private final HttpRequest httpRequest; + + public MicronautHttpRequestEventProcessor(HttpRequest httpRequest) { + this.httpRequest = httpRequest; + } + + // httpRequest.getRequestURL() returns StringBuffer which is considered an obsolete class. + @SuppressWarnings("JdkObsolete") + @Override + public SentryEvent process(SentryEvent event, Object hint) { + final Request sentryRequest = new Request(); + + sentryRequest.setMethod(httpRequest.getMethod().toString()); + sentryRequest.setQueryString(resolveParameters()); + sentryRequest.setUrl(httpRequest.getPath()); + sentryRequest.setHeaders(resolveHeadersMap(httpRequest)); + + event.setRequest(sentryRequest); + return event; + } + + private String resolveParameters() { + return StreamSupport.stream(httpRequest.getParameters().spliterator(), false) + .filter(e -> !SENSITIVE_PARAMS.contains(e.getKey().toUpperCase())) + .map(e -> e.getKey() + "=" + String.join(",", e.getValue())) + .collect(Collectors.joining("&")); + } + + private Map resolveHeadersMap(final HttpRequest request) { + final Map headersMap = new HashMap<>(); + for (String headerName : request.getHeaders().names()) { + // do not copy personal information identifiable headers + if (!SENSITIVE_HEADERS.contains(headerName.toUpperCase())) { + headersMap.put(headerName, String.join(",", request.getHeaders().getAll(headerName))); + } + } + return headersMap; + } + +} diff --git a/subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/http/SentryFilter.java b/subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/http/SentryFilter.java new file mode 100644 index 0000000..8976931 --- /dev/null +++ b/subprojects/micronaut-log4aws/src/main/groovy/com/agorapulse/micronaut/log4aws/http/SentryFilter.java @@ -0,0 +1,54 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2020 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.micronaut.http.HttpRequest; +import io.micronaut.http.MutableHttpResponse; +import io.micronaut.http.annotation.Filter; +import io.micronaut.http.filter.HttpServerFilter; +import io.micronaut.http.filter.ServerFilterChain; +import io.reactivex.Flowable; +import io.sentry.IHub; +import org.reactivestreams.Publisher; + +@Filter("/**") +public class SentryFilter implements HttpServerFilter { + + private final IHub hub; + + public SentryFilter(IHub hub) { + this.hub = hub; + } + + @Override + public Publisher> doFilter(HttpRequest request, ServerFilterChain chain) { + return Flowable + .just(request) + .doOnNext(r -> { + hub.pushScope(); + hub.configureScope(s -> { + s.addEventProcessor(new MicronautHttpRequestEventProcessor(r)); + }); + }) + .switchMap(chain::proceed) + .doOnNext(res -> { + hub.popScope(); + }); + } + +} diff --git a/testprojects/micronaut-log4aws-test/src/main/groovy/com/agorapulse/micronaut/log4aws/test/Log4AwsFactorySpec.groovy b/testprojects/micronaut-log4aws-test/src/main/groovy/com/agorapulse/micronaut/log4aws/test/Log4AwsFactorySpec.groovy index 9e017d5..feed821 100644 --- a/testprojects/micronaut-log4aws-test/src/main/groovy/com/agorapulse/micronaut/log4aws/test/Log4AwsFactorySpec.groovy +++ b/testprojects/micronaut-log4aws-test/src/main/groovy/com/agorapulse/micronaut/log4aws/test/Log4AwsFactorySpec.groovy @@ -18,7 +18,7 @@ package com.agorapulse.micronaut.log4aws.test import io.micronaut.context.ApplicationContext -import io.sentry.SentryClient +import io.sentry.IHub import io.sentry.log4j2.SentryAppender import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.core.LoggerContext @@ -34,7 +34,7 @@ abstract class Log4AwsFactorySpec extends Specification { void 'sentry appender configured'() { expect: - context.getBean(SentryClient) + context.getBean(IHub) context.getBean(SentryAppender) when: