Skip to content

[Logs 3] Add options for Logs #4374

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 13, 2025
Merged
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ import io.sentry.ProfilingTraceData
import io.sentry.Sentry
import io.sentry.SentryEnvelope
import io.sentry.SentryEvent
import io.sentry.SentryLogEvents
import io.sentry.SentryLogEvent
import io.sentry.SentryOptions
import io.sentry.SentryReplayEvent
import io.sentry.Session
@@ -191,7 +191,7 @@ class SessionTrackingIntegrationTest {
TODO("Not yet implemented")
}

override fun captureLogs(events: SentryLogEvents, scope: IScope?, hint: Hint?) {
override fun captureLog(event: SentryLogEvent, scope: IScope?, hint: Hint?) {
TODO("Not yet implemented")
}

Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ sentry.enable-backpressure-handling=true
sentry.enable-spotlight=true
sentry.enablePrettySerializationOutput=false
in-app-includes="io.sentry.samples"
sentry.experimental.logs.enabled=true

# Uncomment and set to true to enable aot compatibility
# This flag disables all AOP related features (i.e. @SentryTransaction, @SentrySpan)
Original file line number Diff line number Diff line change
@@ -187,7 +187,9 @@ class SentryAutoConfigurationTest {
"sentry.cron.default-max-runtime=30",
"sentry.cron.default-timezone=America/New_York",
"sentry.cron.default-failure-issue-threshold=40",
"sentry.cron.default-recovery-threshold=50"
"sentry.cron.default-recovery-threshold=50",
"sentry.experimental.logs.enabled=true",
"sentry.experimental.logs.sample-rate=0.4"
).run {
val options = it.getBean(SentryProperties::class.java)
assertThat(options.readTimeoutMillis).isEqualTo(10)
@@ -232,6 +234,8 @@ class SentryAutoConfigurationTest {
assertThat(options.cron!!.defaultTimezone).isEqualTo("America/New_York")
assertThat(options.cron!!.defaultFailureIssueThreshold).isEqualTo(40L)
assertThat(options.cron!!.defaultRecoveryThreshold).isEqualTo(50L)
assertThat(options.experimental.logs.isEnabled).isEqualTo(true)
assertThat(options.experimental.logs.sampleRate).isEqualTo(0.4)
}
}

Original file line number Diff line number Diff line change
@@ -186,7 +186,9 @@ class SentryAutoConfigurationTest {
"sentry.cron.default-max-runtime=30",
"sentry.cron.default-timezone=America/New_York",
"sentry.cron.default-failure-issue-threshold=40",
"sentry.cron.default-recovery-threshold=50"
"sentry.cron.default-recovery-threshold=50",
"sentry.experimental.logs.enabled=true",
"sentry.experimental.logs.sample-rate=0.4"
).run {
val options = it.getBean(SentryProperties::class.java)
assertThat(options.readTimeoutMillis).isEqualTo(10)
@@ -231,6 +233,8 @@ class SentryAutoConfigurationTest {
assertThat(options.cron!!.defaultTimezone).isEqualTo("America/New_York")
assertThat(options.cron!!.defaultFailureIssueThreshold).isEqualTo(40L)
assertThat(options.cron!!.defaultRecoveryThreshold).isEqualTo(50L)
assertThat(options.experimental.logs.isEnabled).isEqualTo(true)
assertThat(options.experimental.logs.sampleRate).isEqualTo(0.4)
}
}

30 changes: 26 additions & 4 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
@@ -464,6 +464,8 @@ public abstract interface class io/sentry/EventProcessor {

public final class io/sentry/ExperimentalOptions {
public fun <init> (ZLio/sentry/protocol/SdkVersion;)V
public fun getLogs ()Lio/sentry/SentryOptions$Logs;
public fun setLogs (Lio/sentry/SentryOptions$Logs;)V
}

public final class io/sentry/ExternalOptions {
@@ -491,6 +493,7 @@ public final class io/sentry/ExternalOptions {
public fun getIgnoredTransactions ()Ljava/util/List;
public fun getInAppExcludes ()Ljava/util/List;
public fun getInAppIncludes ()Ljava/util/List;
public fun getLogsSampleRate ()Ljava/lang/Double;
public fun getMaxRequestBodySize ()Lio/sentry/SentryOptions$RequestSize;
public fun getPrintUncaughtStackTrace ()Ljava/lang/Boolean;
public fun getProfilesSampleRate ()Ljava/lang/Double;
@@ -505,6 +508,7 @@ public final class io/sentry/ExternalOptions {
public fun getTracesSampleRate ()Ljava/lang/Double;
public fun isCaptureOpenTelemetryEvents ()Ljava/lang/Boolean;
public fun isEnableBackpressureHandling ()Ljava/lang/Boolean;
public fun isEnableLogs ()Ljava/lang/Boolean;
public fun isEnablePrettySerializationOutput ()Ljava/lang/Boolean;
public fun isEnableSpotlight ()Ljava/lang/Boolean;
public fun isEnabled ()Ljava/lang/Boolean;
@@ -519,6 +523,7 @@ public final class io/sentry/ExternalOptions {
public fun setDsn (Ljava/lang/String;)V
public fun setEnableBackpressureHandling (Ljava/lang/Boolean;)V
public fun setEnableDeduplication (Ljava/lang/Boolean;)V
public fun setEnableLogs (Ljava/lang/Boolean;)V
public fun setEnablePrettySerializationOutput (Ljava/lang/Boolean;)V
public fun setEnableSpotlight (Ljava/lang/Boolean;)V
public fun setEnableUncaughtExceptionHandler (Ljava/lang/Boolean;)V
@@ -530,6 +535,7 @@ public final class io/sentry/ExternalOptions {
public fun setIgnoredCheckIns (Ljava/util/List;)V
public fun setIgnoredErrors (Ljava/util/List;)V
public fun setIgnoredTransactions (Ljava/util/List;)V
public fun setLogsSampleRate (Ljava/lang/Double;)V
public fun setMaxRequestBodySize (Lio/sentry/SentryOptions$RequestSize;)V
public fun setPrintUncaughtStackTrace (Ljava/lang/Boolean;)V
public fun setProfilesSampleRate (Ljava/lang/Double;)V
@@ -1001,7 +1007,7 @@ public abstract interface class io/sentry/ISentryClient {
public fun captureException (Ljava/lang/Throwable;Lio/sentry/IScope;)Lio/sentry/protocol/SentryId;
public fun captureException (Ljava/lang/Throwable;Lio/sentry/IScope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;
public abstract fun captureFeedback (Lio/sentry/protocol/Feedback;Lio/sentry/Hint;Lio/sentry/IScope;)Lio/sentry/protocol/SentryId;
public abstract fun captureLogs (Lio/sentry/SentryLogEvents;Lio/sentry/IScope;Lio/sentry/Hint;)V
public abstract fun captureLog (Lio/sentry/SentryLogEvent;Lio/sentry/IScope;Lio/sentry/Hint;)V
public fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;)Lio/sentry/protocol/SentryId;
public fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;Lio/sentry/IScope;)Lio/sentry/protocol/SentryId;
public abstract fun captureProfileChunk (Lio/sentry/ProfileChunk;Lio/sentry/IScope;)Lio/sentry/protocol/SentryId;
@@ -2734,7 +2740,7 @@ public final class io/sentry/SentryClient : io/sentry/ISentryClient {
public fun captureEnvelope (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;
public fun captureEvent (Lio/sentry/SentryEvent;Lio/sentry/IScope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;
public fun captureFeedback (Lio/sentry/protocol/Feedback;Lio/sentry/Hint;Lio/sentry/IScope;)Lio/sentry/protocol/SentryId;
public fun captureLogs (Lio/sentry/SentryLogEvents;Lio/sentry/IScope;Lio/sentry/Hint;)V
public fun captureLog (Lio/sentry/SentryLogEvent;Lio/sentry/IScope;Lio/sentry/Hint;)V
public fun captureProfileChunk (Lio/sentry/ProfileChunk;Lio/sentry/IScope;)Lio/sentry/protocol/SentryId;
public fun captureReplayEvent (Lio/sentry/SentryReplayEvent;Lio/sentry/IScope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;
public fun captureSession (Lio/sentry/Session;Lio/sentry/Hint;)V
@@ -3033,14 +3039,16 @@ public final class io/sentry/SentryLockReason$JsonKeys {
}

public final class io/sentry/SentryLogEvent : io/sentry/JsonSerializable, io/sentry/JsonUnknown {
public fun <init> (Lio/sentry/protocol/SentryId;Lio/sentry/SentryDate;Ljava/lang/String;)V
public fun <init> (Lio/sentry/protocol/SentryId;Ljava/lang/Double;Ljava/lang/String;)V
public fun <init> (Lio/sentry/protocol/SentryId;Lio/sentry/SentryDate;Ljava/lang/String;Lio/sentry/SentryLevel;)V
public fun <init> (Lio/sentry/protocol/SentryId;Ljava/lang/Double;Ljava/lang/String;Lio/sentry/SentryLevel;)V
public fun getAttributes ()Ljava/util/Map;
public fun getBody ()Ljava/lang/String;
public fun getLevel ()Lio/sentry/SentryLevel;
public fun getTimestamp ()Ljava/lang/Double;
public fun getUnknown ()Ljava/util/Map;
public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V
public fun setAttributes (Ljava/util/Map;)V
public fun setBody (Ljava/lang/String;)V
public fun setLevel (Lio/sentry/SentryLevel;)V
public fun setTimestamp (Ljava/lang/Double;)V
public fun setUnknown (Ljava/util/Map;)V
@@ -3423,6 +3431,20 @@ public final class io/sentry/SentryOptions$Cron {
public fun setDefaultTimezone (Ljava/lang/String;)V
}

public final class io/sentry/SentryOptions$Logs {
public fun <init> ()V
public fun getBeforeSend ()Lio/sentry/SentryOptions$Logs$BeforeSendLogCallback;
public fun getSampleRate ()Ljava/lang/Double;
public fun isEnabled ()Z
public fun setBeforeSend (Lio/sentry/SentryOptions$Logs$BeforeSendLogCallback;)V
public fun setEnabled (Z)V
public fun setSampleRate (Ljava/lang/Double;)V
}

public abstract interface class io/sentry/SentryOptions$Logs$BeforeSendLogCallback {
public abstract fun execute (Lio/sentry/SentryLogEvent;Lio/sentry/Hint;)Lio/sentry/SentryLogEvent;
}

public abstract interface class io/sentry/SentryOptions$ProfilesSamplerCallback {
public abstract fun sample (Lio/sentry/SamplingContext;)Ljava/lang/Double;
}
13 changes: 13 additions & 0 deletions sentry/src/main/java/io/sentry/ExperimentalOptions.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.sentry;

import io.sentry.protocol.SdkVersion;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
@@ -10,6 +12,17 @@
* <p>Beware that experimental options can change at any time.
*/
public final class ExperimentalOptions {
private @NotNull SentryOptions.Logs logs = new SentryOptions.Logs();

public ExperimentalOptions(final boolean empty, final @Nullable SdkVersion sdkVersion) {}

@ApiStatus.Experimental
public @NotNull SentryOptions.Logs getLogs() {
return logs;
}

@ApiStatus.Experimental
public void setLogs(@NotNull SentryOptions.Logs logs) {
this.logs = logs;
}
}
25 changes: 25 additions & 0 deletions sentry/src/main/java/io/sentry/ExternalOptions.java
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ public final class ExternalOptions {
private @Nullable Boolean enableDeduplication;
private @Nullable Double tracesSampleRate;
private @Nullable Double profilesSampleRate;
private @Nullable Double logsSampleRate;
private @Nullable SentryOptions.RequestSize maxRequestBodySize;
private final @NotNull Map<String, @NotNull String> tags = new ConcurrentHashMap<>();
private @Nullable SentryOptions.Proxy proxy;
@@ -43,6 +44,7 @@ public final class ExternalOptions {
private @Nullable Boolean enabled;
private @Nullable Boolean enablePrettySerializationOutput;
private @Nullable Boolean enableSpotlight;
private @Nullable Boolean enableLogs;
private @Nullable String spotlightConnectionUrl;

private @Nullable List<String> ignoredCheckIns;
@@ -150,6 +152,9 @@ public final class ExternalOptions {
options.setCaptureOpenTelemetryEvents(
propertiesProvider.getBooleanProperty("capture-open-telemetry-events"));

options.setEnableLogs(propertiesProvider.getBooleanProperty("logs.enabled"));
options.setLogsSampleRate(propertiesProvider.getDoubleProperty("logs.sample-rate"));

for (final String ignoredExceptionType :
propertiesProvider.getList("ignored-exceptions-for-type")) {
try {
@@ -518,4 +523,24 @@ public void setCaptureOpenTelemetryEvents(final @Nullable Boolean captureOpenTel
public @Nullable Boolean isCaptureOpenTelemetryEvents() {
return captureOpenTelemetryEvents;
}

@ApiStatus.Experimental
public void setEnableLogs(final @Nullable Boolean enableLogs) {
this.enableLogs = enableLogs;
}

@ApiStatus.Experimental
public @Nullable Boolean isEnableLogs() {
return enableLogs;
}

@ApiStatus.Experimental
public @Nullable Double getLogsSampleRate() {
return logsSampleRate;
}

@ApiStatus.Experimental
public void setLogsSampleRate(final @Nullable Double logsSampleRate) {
this.logsSampleRate = logsSampleRate;
}
}
2 changes: 1 addition & 1 deletion sentry/src/main/java/io/sentry/ISentryClient.java
Original file line number Diff line number Diff line change
@@ -305,7 +305,7 @@ SentryId captureProfileChunk(
SentryId captureCheckIn(@NotNull CheckIn checkIn, @Nullable IScope scope, @Nullable Hint hint);

@ApiStatus.Experimental
void captureLogs(@NotNull SentryLogEvents logEvents, @Nullable IScope scope, @Nullable Hint hint);
void captureLog(@NotNull SentryLogEvent logEvent, @Nullable IScope scope, @Nullable Hint hint);

@ApiStatus.Internal
@Nullable
4 changes: 2 additions & 2 deletions sentry/src/main/java/io/sentry/NoOpSentryClient.java
Original file line number Diff line number Diff line change
@@ -86,8 +86,8 @@ public SentryId captureEnvelope(@NotNull SentryEnvelope envelope, @Nullable Hint

@ApiStatus.Experimental
@Override
public void captureLogs(
@NotNull SentryLogEvents logEvents, @Nullable IScope scope, @Nullable Hint hint) {
public void captureLog(
@NotNull SentryLogEvent logEvent, @Nullable IScope scope, @Nullable Hint hint) {
// do nothing
}

65 changes: 51 additions & 14 deletions sentry/src/main/java/io/sentry/SentryClient.java
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -1124,28 +1125,42 @@ public void captureSession(final @NotNull Session session, final @Nullable Hint

@ApiStatus.Experimental
@Override
public void captureLogs(
@NotNull SentryLogEvents logEvents, @Nullable IScope scope, @Nullable Hint hint) {
public void captureLog(
@Nullable SentryLogEvent logEvent, @Nullable IScope scope, @Nullable Hint hint) {
if (hint == null) {
hint = new Hint();
}

try {
@Nullable TraceContext traceContext = null;
if (scope != null) {
final @Nullable ITransaction transaction = scope.getTransaction();
if (transaction != null) {
traceContext = transaction.traceContext();
} else {
final @NotNull PropagationContext propagationContext =
TracingUtils.maybeUpdateBaggage(scope, options);
traceContext = propagationContext.traceContext();
}
@Nullable TraceContext traceContext = null;
if (scope != null) {
final @Nullable ITransaction transaction = scope.getTransaction();
if (transaction != null) {
traceContext = transaction.traceContext();
} else {
final @NotNull PropagationContext propagationContext =
TracingUtils.maybeUpdateBaggage(scope, options);
traceContext = propagationContext.traceContext();
}
}

if (logEvent != null) {
logEvent = executeBeforeSendLog(logEvent, hint);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add tests for this beforeSend?


if (logEvent == null) {
options.getLogger().log(SentryLevel.DEBUG, "Log Event was dropped by beforeSendLog");
options
.getClientReportRecorder()
.recordLostEvent(DiscardReason.BEFORE_SEND, DataCategory.LogItem);
return;
}
}

final @NotNull SentryEnvelope envelope = buildEnvelope(logEvents, traceContext);
try {
final @NotNull SentryEnvelope envelope =
buildEnvelope(new SentryLogEvents(Arrays.asList(logEvent)), traceContext);

hint.clear();
// TODO buffer
sendEnvelope(envelope, hint);
} catch (IOException e) {
options.getLogger().log(SentryLevel.WARNING, e, "Capturing log failed.");
@@ -1428,6 +1443,28 @@ private void sortBreadcrumbsByDate(
return event;
}

private @Nullable SentryLogEvent executeBeforeSendLog(
@NotNull SentryLogEvent event, final @NotNull Hint hint) {
final SentryOptions.Logs.BeforeSendLogCallback beforeSendLog =
options.getExperimental().getLogs().getBeforeSend();
if (beforeSendLog != null) {
try {
event = beforeSendLog.execute(event, hint);
} catch (Throwable e) {
options
.getLogger()
.log(
SentryLevel.ERROR,
"The BeforeSendLog callback threw an exception. Dropping log event.",
e);

// drop event in case of an error in beforeSendLog due to PII concerns
event = null;
}
}
return event;
}

@Override
public void close() {
close(false);
Loading