-
-
Notifications
You must be signed in to change notification settings - Fork 30
Add Logback integration. #511
Changes from all commits
6b10e59
5d62b55
88cf092
888273d
b672bba
01e1c20
e03d185
080f0eb
4564551
fbfe4ac
be63b6d
672f048
81fffbc
10a798f
475f70a
b7124b4
fad633a
a51caa9
886dd34
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| 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.logbackClassic) | ||
|
|
||
| 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.logback") | ||
| buildConfigField("String", "SENTRY_LOGBACK_SDK_NAME", "\"${Config.Sentry.SENTRY_LOGBACK_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 | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,200 @@ | ||||||||
| package io.sentry.logback; | ||||||||
|
|
||||||||
| import ch.qos.logback.classic.Level; | ||||||||
| import ch.qos.logback.classic.spi.ILoggingEvent; | ||||||||
| import ch.qos.logback.classic.spi.ThrowableProxy; | ||||||||
| import ch.qos.logback.core.UnsynchronizedAppenderBase; | ||||||||
| import io.sentry.core.DateUtils; | ||||||||
| 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 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 io.sentry.core.transport.ITransport; | ||||||||
| import io.sentry.core.util.CollectionUtils; | ||||||||
| import org.jetbrains.annotations.ApiStatus; | ||||||||
| import org.jetbrains.annotations.NotNull; | ||||||||
| import org.jetbrains.annotations.Nullable; | ||||||||
|
|
||||||||
| /** Appender for logback in charge of sending the logged events to a Sentry server. */ | ||||||||
| public final class SentryAppender extends UnsynchronizedAppenderBase<ILoggingEvent> { | ||||||||
| private @Nullable String dsn; | ||||||||
| private @Nullable String environment; | ||||||||
| private @Nullable Integer maxBreadcrumbs; | ||||||||
| private @Nullable Integer shutdownTimeoutMillis; | ||||||||
| private @Nullable Integer flushTimeoutMillis; | ||||||||
| private @Nullable Integer readTimeoutMillis; | ||||||||
| private @Nullable Double sampleRate; | ||||||||
| private @Nullable Boolean bypassSecurity; | ||||||||
| private @Nullable Boolean debug; | ||||||||
| private @Nullable Boolean attachThreads; | ||||||||
| private @Nullable Boolean attachStacktrace; | ||||||||
| private @Nullable ITransport transport; | ||||||||
|
|
||||||||
| @Override | ||||||||
| public void start() { | ||||||||
| if (dsn != null) { | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need some config here to allow a user to opt out for initializing thought the sdk. It's confusing when the 3 integrations are on and folks don't know what is initing it. If we can also check if the sdk is already initializing and then warn about it. (Like Sentry.isEnabled) would be best. Last init wins is OK so dispose previous bound hub l/Clientand get it init again ur it's often a mistake.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Passing config here is optional. If user has at the same time Spring Boot starter.and configures Sentry through it he can omit completely passing options through Logback
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. Though there's no way to avoid initializing the SDK via this integration, right? That's why the suggestion:
Or at least log a warning with the diagnostic logger |
||||||||
| Sentry.init( | ||||||||
| options -> { | ||||||||
| options.setDsn(dsn); | ||||||||
maciejwalkowiak marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
| Optional.ofNullable(maxBreadcrumbs).ifPresent(options::setMaxBreadcrumbs); | ||||||||
maciejwalkowiak marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
| Optional.ofNullable(environment).ifPresent(options::setEnvironment); | ||||||||
| Optional.ofNullable(shutdownTimeoutMillis).ifPresent(options::setShutdownTimeout); | ||||||||
| Optional.ofNullable(flushTimeoutMillis).ifPresent(options::setFlushTimeoutMillis); | ||||||||
| Optional.ofNullable(readTimeoutMillis).ifPresent(options::setReadTimeoutMillis); | ||||||||
| Optional.ofNullable(sampleRate).ifPresent(options::setSampleRate); | ||||||||
| Optional.ofNullable(bypassSecurity).ifPresent(options::setBypassSecurity); | ||||||||
| Optional.ofNullable(debug).ifPresent(options::setDebug); | ||||||||
| Optional.ofNullable(attachThreads).ifPresent(options::setAttachThreads); | ||||||||
| Optional.ofNullable(attachStacktrace).ifPresent(options::setAttachStacktrace); | ||||||||
| options.setSentryClientName(BuildConfig.SENTRY_LOGBACK_SDK_NAME); | ||||||||
| options.setSdkVersion(createSdkVersion(options)); | ||||||||
| Optional.ofNullable(transport).ifPresent(options::setTransport); | ||||||||
| }); | ||||||||
| } | ||||||||
| super.start(); | ||||||||
| } | ||||||||
|
|
||||||||
| @Override | ||||||||
| protected void append(@NotNull ILoggingEvent eventObject) { | ||||||||
| Sentry.captureEvent(createEvent(eventObject)); | ||||||||
| } | ||||||||
|
|
||||||||
| /** | ||||||||
| * Creates {@link SentryEvent} from Logback's {@link ILoggingEvent}. | ||||||||
| * | ||||||||
| * @param loggingEvent the logback event | ||||||||
| * @return the sentry event | ||||||||
| */ | ||||||||
| @SuppressWarnings("JdkObsolete") | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this suppress? Maybe a note will help folks reading the code
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because we're using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We cannot access diagnostic logger from this level as it's a part of
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. re: Obsolete. That description you gave here would fit well as a comment. So it's clear why we ignored it. re: logger. Sounds good to at least log on sentry.init! |
||||||||
| final @NotNull SentryEvent createEvent(@NotNull ILoggingEvent loggingEvent) { | ||||||||
| final SentryEvent event = new SentryEvent(DateUtils.getDateTime(new Date(loggingEvent.getTimeStamp()))); | ||||||||
| final Message message = new Message(); | ||||||||
| message.setMessage(loggingEvent.getMessage()); | ||||||||
| message.setFormatted(loggingEvent.getFormattedMessage()); | ||||||||
maciejwalkowiak marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
| message.setParams(toParams(loggingEvent.getArgumentArray())); | ||||||||
| event.setMessage(message); | ||||||||
| event.setLogger(loggingEvent.getLoggerName()); | ||||||||
| event.setLevel(formatLevel(loggingEvent.getLevel())); | ||||||||
|
|
||||||||
| final ThrowableProxy throwableInformation = (ThrowableProxy) loggingEvent.getThrowableProxy(); | ||||||||
| if (throwableInformation != null) { | ||||||||
| event.setThrowable(throwableInformation.getThrowable()); | ||||||||
| } | ||||||||
|
|
||||||||
| if (loggingEvent.getThreadName() != null) { | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We actually have a We tend to avoid using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, got the point. The question is - do we drop it and rely on My preference would be to add
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what about we add a single thread to the list then? with id, name? When the attachThreads is false that is |
||||||||
| event.setExtra("thread_name", loggingEvent.getThreadName()); | ||||||||
| } | ||||||||
|
|
||||||||
| final Map<String, String> mdcProperties = CollectionUtils.shallowCopy(loggingEvent.getMDCPropertyMap()); | ||||||||
| if (!mdcProperties.isEmpty()) { | ||||||||
| event.getContexts().put("MDC", mdcProperties); | ||||||||
| } | ||||||||
|
|
||||||||
| return event; | ||||||||
| } | ||||||||
|
|
||||||||
| private @NotNull List<String> toParams(@Nullable Object[] arguments) { | ||||||||
| if (arguments != null) { | ||||||||
| return Arrays.stream(arguments) | ||||||||
| .filter(Objects::nonNull) | ||||||||
| .map(Object::toString) | ||||||||
| .collect(Collectors.toList()); | ||||||||
| } else { | ||||||||
| return Collections.emptyList(); | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| /** | ||||||||
| * 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(@NotNull Level level) { | ||||||||
| if (level.isGreaterOrEqual(Level.ERROR)) { | ||||||||
bruno-garcia marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
| return SentryLevel.ERROR; | ||||||||
| } else if (level.isGreaterOrEqual(Level.WARN)) { | ||||||||
| return SentryLevel.WARNING; | ||||||||
| } else if (level.isGreaterOrEqual(Level.INFO)) { | ||||||||
| return SentryLevel.INFO; | ||||||||
| } else { | ||||||||
| return SentryLevel.DEBUG; | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| private @NotNull SdkVersion createSdkVersion(@NotNull SentryOptions sentryOptions) { | ||||||||
| SdkVersion sdkVersion = sentryOptions.getSdkVersion(); | ||||||||
|
|
||||||||
| if (sdkVersion == null) { | ||||||||
| sdkVersion = new SdkVersion(); | ||||||||
| } | ||||||||
|
|
||||||||
| sdkVersion.setName(BuildConfig.SENTRY_LOGBACK_SDK_NAME); | ||||||||
| final String version = BuildConfig.VERSION_NAME; | ||||||||
| sdkVersion.setVersion(version); | ||||||||
| sdkVersion.addPackage("maven:sentry-logback", version); | ||||||||
bruno-garcia marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
|
||||||||
| return sdkVersion; | ||||||||
| } | ||||||||
|
|
||||||||
| public void setDsn(@Nullable String dsn) { | ||||||||
| this.dsn = dsn; | ||||||||
| } | ||||||||
|
|
||||||||
| public void setEnvironment(@Nullable String environment) { | ||||||||
| this.environment = environment; | ||||||||
| } | ||||||||
|
|
||||||||
| public void setMaxBreadcrumbs(@Nullable Integer maxBreadcrumbs) { | ||||||||
| this.maxBreadcrumbs = maxBreadcrumbs; | ||||||||
| } | ||||||||
|
|
||||||||
| public void setShutdownTimeoutMillis(@Nullable Integer shutdownTimeoutMillis) { | ||||||||
| this.shutdownTimeoutMillis = shutdownTimeoutMillis; | ||||||||
| } | ||||||||
|
|
||||||||
| public void setFlushTimeoutMillis(@Nullable Integer flushTimeoutMillis) { | ||||||||
| this.flushTimeoutMillis = flushTimeoutMillis; | ||||||||
| } | ||||||||
|
|
||||||||
| public void setReadTimeoutMillis(@Nullable Integer readTimeoutMillis) { | ||||||||
| this.readTimeoutMillis = readTimeoutMillis; | ||||||||
| } | ||||||||
|
|
||||||||
| public void setSampleRate(@Nullable Double sampleRate) { | ||||||||
| this.sampleRate = sampleRate; | ||||||||
| } | ||||||||
|
|
||||||||
| public void setBypassSecurity(@Nullable Boolean bypassSecurity) { | ||||||||
| this.bypassSecurity = bypassSecurity; | ||||||||
| } | ||||||||
|
|
||||||||
| public void setDebug(@Nullable Boolean debug) { | ||||||||
| this.debug = debug; | ||||||||
| } | ||||||||
|
|
||||||||
| public void setAttachThreads(@Nullable Boolean attachThreads) { | ||||||||
| this.attachThreads = attachThreads; | ||||||||
| } | ||||||||
|
|
||||||||
| public void setAttachStacktrace(@Nullable Boolean attachStacktrace) { | ||||||||
| this.attachStacktrace = attachStacktrace; | ||||||||
| } | ||||||||
|
|
||||||||
| @ApiStatus.Internal | ||||||||
| void setTransport(@Nullable ITransport transport) { | ||||||||
| this.transport = transport; | ||||||||
| } | ||||||||
| } | ||||||||
Uh oh!
There was an error while loading. Please reload this page.