Skip to content
This repository was archived by the owner on Aug 30, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
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
23 changes: 23 additions & 0 deletions sentry-core/src/main/java/io/sentry/core/SentryClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class SentryClient implements ISentryClient {
static final String SENTRY_PROTOCOL_VERSION = "7";
Expand All @@ -17,6 +18,7 @@ public class SentryClient implements ISentryClient {

private final SentryOptions options;
private final AsyncConnection connection;
private final Random random;

public boolean isEnabled() {
return isEnabled;
Expand All @@ -33,9 +35,19 @@ public SentryClient(SentryOptions options, @Nullable AsyncConnection connection)
connection = AsyncConnectionFactory.create(options);
}
this.connection = connection;
random = options.getSampling() == null ? null : new Random();
}

public SentryId captureEvent(SentryEvent event, @Nullable Scope scope) {
if (!sample()) {
log(
options.getLogger(),
SentryLevel.DEBUG,
"Event %s was dropped due to sampling decision.",
event.getEventId());
return SentryId.EMPTY_ID;
}

log(options.getLogger(), SentryLevel.DEBUG, "Capturing event: %s", event.getEventId());

if (scope != null) {
Expand Down Expand Up @@ -128,4 +140,15 @@ public void close() {
public void flush(long timeoutMills) {
// TODO: Flush transport
}

private boolean sample() {
// https://docs.sentry.io/development/sdk-dev/features/#event-sampling
if (options.getSampling() != null && random != null) {
double sampling = options.getSampling();
if (sampling < random.nextDouble()) {
return false; // bad luck
}
}
return true;
}
}
16 changes: 16 additions & 0 deletions sentry-core/src/main/java/io/sentry/core/SentryOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class SentryOptions {
private String release;
private String environment;
private Proxy proxy;
private Double sampling;

public void addEventProcessor(EventProcessor eventProcessor) {
eventProcessors.add(eventProcessor);
Expand Down Expand Up @@ -171,6 +172,21 @@ public void setProxy(Proxy proxy) {
this.proxy = proxy;
}

public Double getSampling() {
return sampling;
}

// Can be anything between 0.01 (1%) and 1.0 (99.9%) or null (default), to disable it.
public void setSampling(Double sampling) {
if (sampling != null && (sampling > 1.0 || sampling <= 0.0)) {
throw new IllegalArgumentException(
"The value "
+ sampling
+ " is not valid. Use null to disable or values between 0.01 (inclusive) and 1.0 (exclusive).");
}
this.sampling = sampling;
}

public interface BeforeSendCallback {
SentryEvent execute(SentryEvent event);
}
Expand Down
21 changes: 21 additions & 0 deletions sentry-core/src/test/java/io/sentry/core/SentryClientTest.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.sentry.core

import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.mockingDetails
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
Expand Down Expand Up @@ -239,6 +240,26 @@ class SentryClientTest {
assertEquals(SentryLevel.FATAL, event.level)
}

@Test
fun `when captureEvent with sampling, some events not captured`() {
fixture.sentryOptions.sampling = 0.000000001
val sut = fixture.getSut()

val allEvents = 10
(0..allEvents).forEach { _ -> sut.captureEvent(SentryEvent()) }
assertTrue(allEvents > mockingDetails(fixture.connection).invocations.size)
}

@Test
fun `when captureEvent without sampling, all events are captured`() {
fixture.sentryOptions.sampling = null
val sut = fixture.getSut()

val allEvents = 10
(0..allEvents).forEach { _ -> sut.captureEvent(SentryEvent()) }
assertEquals(allEvents, mockingDetails(fixture.connection).invocations.size - 1) // 1 extra invocation outside .send()
}

private fun createScope(): Scope {
return Scope(fixture.sentryOptions.maxBreadcrumbs).apply {
addBreadcrumb(Breadcrumb().apply {
Expand Down
35 changes: 35 additions & 0 deletions sentry-core/src/test/java/io/sentry/core/SentryOptionsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package io.sentry.core

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue

class SentryOptionsTest {
Expand Down Expand Up @@ -51,4 +53,37 @@ class SentryOptionsTest {
options.maxBreadcrumbs = 1
assertEquals(1, options.maxBreadcrumbs)
}

@Test
fun `when options is initialized, default sampling is null`() =
assertNull(SentryOptions().sampling)

@Test
fun `when setSampling is called, overrides default`() {
val options = SentryOptions()
options.sampling = 0.5
assertEquals(0.5, options.sampling)
}

@Test
fun `when setSampling is called with null, disables it`() {
val options = SentryOptions()
options.sampling = null
assertNull(options.sampling)
}

@Test
fun `when setSampling is set to higher than 1_0, setter throws`() {
assertFailsWith<IllegalArgumentException> { SentryOptions().sampling = 1.0000000000001 }
}

@Test
fun `when setSampling is set to lower than 0, setter throws`() {
assertFailsWith<IllegalArgumentException> { SentryOptions().sampling = -0.0000000000001 }
}

@Test
fun `when setSampling is set to exactly 0, setter throws`() {
assertFailsWith<IllegalArgumentException> { SentryOptions().sampling = 0.0 }
}
}