Skip to content
This repository has been archived by the owner on Aug 30, 2023. It is now read-only.

Commit

Permalink
Add Log4j2 integration. (#537)
Browse files Browse the repository at this point in the history
  • Loading branch information
maciejwalkowiak committed Sep 2, 2020
1 parent 82e4efe commit 35750dd
Show file tree
Hide file tree
Showing 10 changed files with 703 additions and 0 deletions.
5 changes: 5 additions & 0 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ object Config {
val logbackVersion = "1.2.3"
val logbackClassic = "ch.qos.logback:logback-classic:$logbackVersion"

val log4j2Version = "2.13.3"
val log4j2Api = "org.apache.logging.log4j:log4j-api:$log4j2Version"
val log4j2Core = "org.apache.logging.log4j:log4j-core:$log4j2Version"

val springBootStarter = "org.springframework.boot:spring-boot-starter:$springBootVersion"

val springWeb = "org.springframework:spring-webmvc"
Expand Down Expand Up @@ -84,6 +88,7 @@ object Config {
val SENTRY_JAVA_SDK_NAME = "sentry.java"
val SENTRY_ANDROID_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.android"
val SENTRY_LOGBACK_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.logback"
val SENTRY_LOG4J2_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.log4j2"
val SENTRY_SPRING_BOOT_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot"
val group = "io.sentry"
val description = "SDK for sentry.io"
Expand Down
103 changes: 103 additions & 0 deletions sentry-log4j2/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import com.novoda.gradle.release.PublishExtension

plugins {
`java-library`
kotlin("jvm")
jacoco
id(Config.QualityPlugins.errorProne)
id(Config.Deploy.novodaBintray)
id(Config.QualityPlugins.gradleVersions)
id(Config.BuildPlugins.buildConfig) version Config.BuildPlugins.buildConfigVersion
}

configure<JavaPluginConvention> {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
}

dependencies {
api(project(":sentry-core"))
implementation(Config.Libs.log4j2Api)
implementation(Config.Libs.log4j2Core)

compileOnly(Config.CompileOnly.nopen)
errorprone(Config.CompileOnly.nopenChecker)
errorprone(Config.CompileOnly.errorprone)
errorproneJavac(Config.CompileOnly.errorProneJavac8)
compileOnly(Config.CompileOnly.jetbrainsAnnotations)

// tests
testImplementation(kotlin(Config.kotlinStdLib))
testImplementation(Config.TestLibs.kotlinTestJunit)
testImplementation(Config.TestLibs.mockitoKotlin)
testImplementation(Config.TestLibs.awaitility)
}

configure<SourceSetContainer> {
test {
java.srcDir("src/test/java")
}
}

jacoco {
toolVersion = Config.QualityPlugins.jacocoVersion
}

tasks.jacocoTestReport {
reports {
xml.isEnabled = true
html.isEnabled = false
}
}

tasks {
jacocoTestCoverageVerification {
violationRules {
rule { limit { minimum = BigDecimal.valueOf(0.6) } }
}
}
check {
dependsOn(jacocoTestCoverageVerification)
dependsOn(jacocoTestReport)
}
}

buildConfig {
useJavaOutput()
packageName("io.sentry.log4j2")
buildConfigField("String", "SENTRY_LOG4J2_SDK_NAME", "\"${Config.Sentry.SENTRY_LOG4J2_SDK_NAME}\"")
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
}

val generateBuildConfig by tasks
tasks.withType<JavaCompile>().configureEach {
dependsOn(generateBuildConfig)
}

//TODO: move these blocks to parent gradle file, DRY
configure<PublishExtension> {
userOrg = Config.Sentry.userOrg
groupId = project.group.toString()
publishVersion = project.version.toString()
desc = Config.Sentry.description
website = Config.Sentry.website
repoName = Config.Sentry.repoName
setLicences(Config.Sentry.licence)
setLicenceUrls(Config.Sentry.licenceUrl)
issueTracker = Config.Sentry.issueTracker
repository = Config.Sentry.repository
sign = Config.Deploy.sign
artifactId = project.name
uploadName = "${project.group}:${project.name}"
devId = Config.Sentry.userOrg
devName = Config.Sentry.devName
devEmail = Config.Sentry.devEmail
scmConnection = Config.Sentry.scmConnection
scmDevConnection = Config.Sentry.scmDevConnection
scmUrl = Config.Sentry.scmUrl
}

206 changes: 206 additions & 0 deletions sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package io.sentry.log4j2;

import io.sentry.core.Breadcrumb;
import io.sentry.core.DateUtils;
import io.sentry.core.HubAdapter;
import io.sentry.core.IHub;
import io.sentry.core.Sentry;
import io.sentry.core.SentryEvent;
import io.sentry.core.SentryLevel;
import io.sentry.core.SentryOptions;
import io.sentry.core.protocol.Message;
import io.sentry.core.protocol.SdkVersion;
import io.sentry.core.transport.ITransport;
import io.sentry.core.util.CollectionUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.impl.ThrowableProxy;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/** Appender for Log4j2 in charge of sending the logged events to a Sentry server. */
@Plugin(name = "Sentry", category = "Core", elementType = "appender", printObject = true)
public final class SentryAppender extends AbstractAppender {
private final @Nullable String dsn;
private final @Nullable ITransport transport;
private @NotNull Level minimumBreadcrumbLevel = Level.INFO;
private @NotNull Level minimumEventLevel = Level.ERROR;
private final @NotNull IHub hub;

public SentryAppender(
final @NotNull String name,
final @Nullable Filter filter,
final @Nullable String dsn,
final @Nullable Level minimumBreadcrumbLevel,
final @Nullable Level minimumEventLevel,
final @Nullable ITransport transport,
final @NotNull IHub hub) {
super(name, filter, null, true, null);
this.dsn = dsn;
if (minimumBreadcrumbLevel != null) {
this.minimumBreadcrumbLevel = minimumBreadcrumbLevel;
}
if (minimumEventLevel != null) {
this.minimumEventLevel = minimumEventLevel;
}
this.transport = transport;
this.hub = hub;
}

/**
* Create a Sentry Appender.
*
* @param name The name of the Appender.
* @param filter The filter, if any, to use.
* @return The SentryAppender.
*/
@PluginFactory
public static SentryAppender createAppender(
@PluginAttribute("name") final String name,
@PluginAttribute("minimumBreadcrumbLevel") final Level minimumBreadcrumbLevel,
@PluginAttribute("minimumEventLevel") final Level minimumEventLevel,
@PluginAttribute("dsn") final String dsn,
@PluginElement("filter") final Filter filter) {

if (name == null) {
LOGGER.error("No name provided for SentryAppender");
return null;
}
return new SentryAppender(name, filter, dsn, minimumBreadcrumbLevel, minimumEventLevel, null, HubAdapter.getInstance());
}

@Override
public void start() {
if (dsn != null) {
Sentry.init(
options -> {
options.setDsn(dsn);
options.setSentryClientName(BuildConfig.SENTRY_LOG4J2_SDK_NAME);
options.setSdkVersion(createSdkVersion(options));
Optional.ofNullable(transport).ifPresent(options::setTransport);
});
}
super.start();
}

@Override
public void append(final @NotNull LogEvent eventObject) {
if (eventObject.getLevel().isMoreSpecificThan(minimumEventLevel)) {
hub.captureEvent(createEvent(eventObject));
}
if (eventObject.getLevel().isMoreSpecificThan(minimumBreadcrumbLevel)) {
hub.addBreadcrumb(createBreadcrumb(eventObject));
}
}

/**
* Creates {@link SentryEvent} from Log4j2 {@link LogEvent}.
*
* @param loggingEvent the log4j2 event
* @return the sentry event
*/
// for the Android compatibility we must use old Java Date class
@SuppressWarnings("JdkObsolete")
final @NotNull SentryEvent createEvent(final @NotNull LogEvent loggingEvent) {
final SentryEvent event =
new SentryEvent(DateUtils.getDateTime(new Date(loggingEvent.getTimeMillis())));
final Message message = new Message();
message.setMessage(loggingEvent.getMessage().getFormat());
message.setFormatted(loggingEvent.getMessage().getFormattedMessage());
message.setParams(toParams(loggingEvent.getMessage().getParameters()));
event.setMessage(message);
event.setLogger(loggingEvent.getLoggerName());
event.setLevel(formatLevel(loggingEvent.getLevel()));

final ThrowableProxy throwableInformation = loggingEvent.getThrownProxy();
if (throwableInformation != null) {
event.setThrowable(throwableInformation.getThrowable());
}

if (loggingEvent.getThreadName() != null) {
event.setExtra("thread_name", loggingEvent.getThreadName());
}

final Map<String, String> contextData =
CollectionUtils.shallowCopy(loggingEvent.getContextData().toMap());
if (!contextData.isEmpty()) {
event.getContexts().put("Context Data", contextData);
}

return event;
}

private @NotNull List<String> toParams(final @Nullable Object[] arguments) {
if (arguments != null) {
return Arrays.stream(arguments)
.filter(Objects::nonNull)
.map(Object::toString)
.collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}

/**
* Creates {@link Breadcrumb} from log4j2 {@link LogEvent}.
*
* @param loggingEvent the log4j2 event
* @return the sentry breadcrumb
*/
private @NotNull Breadcrumb createBreadcrumb(final @NotNull LogEvent loggingEvent) {
final Breadcrumb breadcrumb = new Breadcrumb();
breadcrumb.setLevel(formatLevel(loggingEvent.getLevel()));
breadcrumb.setCategory(loggingEvent.getLoggerName());
breadcrumb.setMessage(loggingEvent.getMessage().getFormattedMessage());
return breadcrumb;
}

/**
* Transforms a {@link Level} into an {@link SentryLevel}.
*
* @param level original level as defined in log4j.
* @return log level used within sentry.
*/
private static @NotNull SentryLevel formatLevel(final @NotNull Level level) {
if (level.isMoreSpecificThan(Level.FATAL)) {
return SentryLevel.FATAL;
} else if (level.isMoreSpecificThan(Level.ERROR)) {
return SentryLevel.ERROR;
} else if (level.isMoreSpecificThan(Level.WARN)) {
return SentryLevel.WARNING;
} else if (level.isMoreSpecificThan(Level.INFO)) {
return SentryLevel.INFO;
} else {
return SentryLevel.DEBUG;
}
}

private @NotNull SdkVersion createSdkVersion(final @NotNull SentryOptions sentryOptions) {
SdkVersion sdkVersion = sentryOptions.getSdkVersion();

if (sdkVersion == null) {
sdkVersion = new SdkVersion();
}

sdkVersion.setName(BuildConfig.SENTRY_LOG4J2_SDK_NAME);
final String version = BuildConfig.VERSION_NAME;
sdkVersion.setVersion(version);
sdkVersion.addPackage("maven:sentry-log4j2", version);

return sdkVersion;
}
}
Loading

0 comments on commit 35750dd

Please sign in to comment.