Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions sentry-jul/api/sentry-jul.api
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ public class io/sentry/jul/SentryHandler : java/util/logging/Handler {
public fun <init> ()V
public fun <init> (Lio/sentry/SentryOptions;)V
public fun <init> (Lio/sentry/SentryOptions;Z)V
protected fun captureLog (Ljava/util/logging/LogRecord;)V
public fun close ()V
public fun flush ()V
public fun getMinimumBreadcrumbLevel ()Ljava/util/logging/Level;
public fun getMinimumEventLevel ()Ljava/util/logging/Level;
public fun getMinimumLevel ()Ljava/util/logging/Level;
public fun isPrintfStyle ()Z
public fun publish (Ljava/util/logging/LogRecord;)V
public fun setMinimumBreadcrumbLevel (Ljava/util/logging/Level;)V
public fun setMinimumEventLevel (Ljava/util/logging/Level;)V
public fun setMinimumLevel (Ljava/util/logging/Level;)V
public fun setPrintfStyle (Z)V
}

82 changes: 82 additions & 0 deletions sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@
import io.sentry.InitPriority;
import io.sentry.ScopesAdapter;
import io.sentry.Sentry;
import io.sentry.SentryAttribute;
import io.sentry.SentryAttributes;
import io.sentry.SentryEvent;
import io.sentry.SentryIntegrationPackageStorage;
import io.sentry.SentryLevel;
import io.sentry.SentryLogLevel;
import io.sentry.SentryOptions;
import io.sentry.exception.ExceptionMechanismException;
import io.sentry.logger.SentryLogParameters;
import io.sentry.protocol.Mechanism;
import io.sentry.protocol.Message;
import io.sentry.protocol.SdkVersion;
Expand Down Expand Up @@ -50,6 +54,7 @@ public class SentryHandler extends Handler {

private @NotNull Level minimumBreadcrumbLevel = Level.INFO;
private @NotNull Level minimumEventLevel = Level.SEVERE;
private @NotNull Level minimumLevel = Level.INFO;

static {
SentryIntegrationPackageStorage.getInstance()
Expand Down Expand Up @@ -106,6 +111,9 @@ public void publish(final @NotNull LogRecord record) {
return;
}
try {
if (record.getLevel().intValue() >= minimumLevel.intValue()) {
captureLog(record);
}
if (record.getLevel().intValue() >= minimumEventLevel.intValue()) {
final Hint hint = new Hint();
hint.set(SENTRY_SYNTHETIC_EXCEPTION, record);
Expand All @@ -126,6 +134,46 @@ public void publish(final @NotNull LogRecord record) {
}
}

/**
* Captures a Sentry log from JULs {@link LogRecord}.
*
* @param loggingEvent the JUL log record
*/
// for the Android compatibility we must use old Java Date class
@SuppressWarnings("JdkObsolete")
protected void captureLog(@NotNull LogRecord loggingEvent) {
final @NotNull SentryLogLevel sentryLevel = toSentryLogLevel(loggingEvent.getLevel());

final @Nullable Object[] arguments = loggingEvent.getParameters();
final @NotNull SentryAttributes attributes = SentryAttributes.of();

@NotNull String message = loggingEvent.getMessage();
if (loggingEvent.getResourceBundle() != null
&& loggingEvent.getResourceBundle().containsKey(loggingEvent.getMessage())) {
message = loggingEvent.getResourceBundle().getString(loggingEvent.getMessage());
}

attributes.add(SentryAttribute.stringAttribute("sentry.message.template", message));

final @NotNull String formattedMessage = maybeFormatted(arguments, message);
final @NotNull SentryLogParameters params = SentryLogParameters.create(attributes);

Sentry.logger().log(sentryLevel, params, formattedMessage, arguments);
}

private @NotNull String maybeFormatted(
final @NotNull Object[] arguments, final @NotNull String message) {
if (arguments != null) {
try {
return formatMessage(message, arguments);
} catch (RuntimeException e) {
// local formatting failed, sending raw message instead of formatted message
}
}

return message;
}

/** Retrieves the properties of the logger. */
private void retrieveProperties() {
final LogManager manager = LogManager.getLogManager();
Expand All @@ -141,6 +189,10 @@ private void retrieveProperties() {
if (minimumEventLevel != null) {
setMinimumEventLevel(parseLevelOrDefault(minimumEventLevel));
}
final String minimumLevel = manager.getProperty(className + ".minimumLevel");
if (minimumLevel != null) {
setMinimumLevel(parseLevelOrDefault(minimumLevel));
}
}

/**
Expand All @@ -163,6 +215,26 @@ private void retrieveProperties() {
}
}

/**
* Transforms a {@link Level} into an {@link SentryLogLevel}.
*
* @param level original level as defined in JUL.
* @return log level used within sentry logs.
*/
private static @NotNull SentryLogLevel toSentryLogLevel(final @NotNull Level level) {
if (level.intValue() >= Level.SEVERE.intValue()) {
return SentryLogLevel.ERROR;
} else if (level.intValue() >= Level.WARNING.intValue()) {
return SentryLogLevel.WARN;
} else if (level.intValue() >= Level.INFO.intValue()) {
return SentryLogLevel.INFO;
} else if (level.intValue() >= Level.FINE.intValue()) {
return SentryLogLevel.DEBUG;
} else {
return SentryLogLevel.TRACE;
}
}

private @NotNull Level parseLevelOrDefault(final @NotNull String levelName) {
try {
return Level.parse(levelName.trim());
Expand Down Expand Up @@ -339,6 +411,16 @@ public void setMinimumEventLevel(final @Nullable Level minimumEventLevel) {
return minimumEventLevel;
}

public void setMinimumLevel(final @Nullable Level minimumLevel) {
if (minimumLevel != null) {
this.minimumLevel = minimumLevel;
}
}

public @NotNull Level getMinimumLevel() {
return minimumLevel;
}

public boolean isPrintfStyle() {
return printfStyle;
}
Expand Down
70 changes: 70 additions & 0 deletions sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package io.sentry.jul
import io.sentry.InitPriority
import io.sentry.Sentry
import io.sentry.SentryLevel
import io.sentry.SentryLogLevel
import io.sentry.SentryOptions
import io.sentry.checkEvent
import io.sentry.checkLogs
import io.sentry.test.initForTest
import io.sentry.transport.ITransport
import java.time.Instant
Expand All @@ -29,6 +31,7 @@ class SentryHandlerTest {
private class Fixture(
minimumBreadcrumbLevel: Level? = null,
minimumEventLevel: Level? = null,
minimumLevel: Level? = null,
val configureWithLogManager: Boolean = false,
val transport: ITransport = mock(),
contextTags: List<String>? = null,
Expand All @@ -45,6 +48,7 @@ class SentryHandlerTest {
handler = SentryHandler(options, configureWithLogManager, true)
handler.setMinimumBreadcrumbLevel(minimumBreadcrumbLevel)
handler.setMinimumEventLevel(minimumEventLevel)
handler.setMinimumLevel(minimumLevel)
handler.level = Level.ALL
logger.handlers.forEach { logger.removeHandler(it) }
logger.addHandler(handler)
Expand Down Expand Up @@ -401,4 +405,70 @@ class SentryHandlerTest {
anyOrNull(),
)
}

@Test
fun `converts finest log level to Sentry log level`() {
fixture = Fixture(minimumLevel = Level.FINEST)
fixture.logger.finest("testing trace level")

Sentry.flush(1000)

verify(fixture.transport)
.send(checkLogs { event -> assertEquals(SentryLogLevel.TRACE, event.items.first().level) })
}

@Test
fun `converts fine log level to Sentry log level`() {
fixture = Fixture(minimumLevel = Level.FINE)
fixture.logger.fine("testing trace level")

Sentry.flush(1000)

verify(fixture.transport)
.send(checkLogs { event -> assertEquals(SentryLogLevel.DEBUG, event.items.first().level) })
}

@Test
fun `converts config log level to Sentry log level`() {
fixture = Fixture(minimumLevel = Level.CONFIG)
fixture.logger.config("testing debug level")

Sentry.flush(1000)

verify(fixture.transport)
.send(checkLogs { event -> assertEquals(SentryLogLevel.DEBUG, event.items.first().level) })
}

@Test
fun `converts info log level to Sentry log level`() {
fixture = Fixture(minimumLevel = Level.INFO)
fixture.logger.info("testing info level")

Sentry.flush(1000)

verify(fixture.transport)
.send(checkLogs { event -> assertEquals(SentryLogLevel.INFO, event.items.first().level) })
}

@Test
fun `converts warn log level to Sentry log level`() {
fixture = Fixture(minimumLevel = Level.WARNING)
fixture.logger.warning("testing warn level")

Sentry.flush(1000)

verify(fixture.transport)
.send(checkLogs { event -> assertEquals(SentryLogLevel.WARN, event.items.first().level) })
}

@Test
fun `converts severe log level to Sentry log level`() {
fixture = Fixture(minimumLevel = Level.SEVERE)
fixture.logger.severe("testing error level")

Sentry.flush(1000)

verify(fixture.transport)
.send(checkLogs { event -> assertEquals(SentryLogLevel.ERROR, event.items.first().level) })
}
}
1 change: 1 addition & 0 deletions sentry-jul/src/test/resources/logging.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
io.sentry.jul.SentryHandler.level=ALL
io.sentry.jul.SentryHandler.minimumEventLevel=WARNING
io.sentry.jul.SentryHandler.minimumBreadcrumbLevel=CONFIG
io.sentry.jul.SentryHandler.minimumLevel=CONFIG
io.sentry.jul.SentryHandler.printfStyle=true

jul.SentryHandlerTest.handlers=java.util.logging.ConsoleHandler, io.sentry.jul.SentryHandler
1 change: 1 addition & 0 deletions sentry-jul/src/test/resources/sentry.properties
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
release=release from sentry.properties
logs.enabled=true
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
io.sentry.jul.SentryHandler.minimumEventLevel=DEBUG
io.sentry.jul.SentryHandler.minimumEventLevel=FINE
io.sentry.jul.SentryHandler.minimumBreadcrumbLevel=CONFIG
io.sentry.jul.SentryHandler.minimumLevel=FINE
io.sentry.jul.SentryHandler.printfStyle=true
io.sentry.jul.SentryHandler.level=CONFIG
handlers=io.sentry.jul.SentryHandler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ debug=true
environment=staging
in-app-includes=io.sentry.samples
context-tags=userId,requestId
logs.enabled=true
Loading