ret = new ArrayList<>(allCachedEvents.length);
-
- for (File f : allCachedEvents) {
- try (final Reader reader =
- new BufferedReader(new InputStreamReader(new FileInputStream(f), UTF_8))) {
-
- ret.add(serializer.deserializeEvent(reader));
- } catch (FileNotFoundException e) {
- options
- .getLogger()
- .log(
- DEBUG,
- "Event file '%s' disappeared while converting all cached files to events.",
- f.getAbsolutePath());
- } catch (IOException e) {
- options
- .getLogger()
- .log(
- ERROR,
- format("Error while reading cached event from file %s", f.getAbsolutePath()),
- e);
- }
- }
-
- return ret.iterator();
- }
-
- private File[] allEventFiles() {
- if (isDirectoryValid()) {
- // TODO: we need to order by oldest to the newest here
- return directory.listFiles((__, fileName) -> fileName.endsWith(FILE_SUFFIX));
- }
- return new File[] {};
- }
-}
diff --git a/sentry-core/src/main/java/io/sentry/core/cache/IEventCache.java b/sentry-core/src/main/java/io/sentry/core/cache/IEventCache.java
deleted file mode 100644
index 6a29ebe0a..000000000
--- a/sentry-core/src/main/java/io/sentry/core/cache/IEventCache.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package io.sentry.core.cache;
-
-import io.sentry.core.SentryEvent;
-
-/**
- * Implementations of this interface are used as a kind of persistent storage for events that wait
- * to be sent to the Sentry server.
- *
- * Note that this interface doesn't handle the situation of resending the stored events after a
- * crash. While that is surely one of the main usecases for the persistent storage of events, the
- * re-initialization is out of scope of the event transport logic.
- */
-public interface IEventCache extends Iterable {
-
- /**
- * Stores the event so that it can be sent later.
- *
- * @param event the event to store
- */
- void store(SentryEvent event);
-
- /**
- * Discards the event from the storage. This means that the event has been successfully sent. Note
- * that this MUST NOT fail on events that haven't been stored before (i.e. this method is called
- * even for events that has been sent on the first attempt).
- *
- * @param event the event to discard from storage
- */
- void discard(SentryEvent event);
-}
diff --git a/sentry-core/src/main/java/io/sentry/core/hints/SessionUpdate.java b/sentry-core/src/main/java/io/sentry/core/hints/SessionUpdate.java
deleted file mode 100644
index 85d843ed1..000000000
--- a/sentry-core/src/main/java/io/sentry/core/hints/SessionUpdate.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package io.sentry.core.hints;
-
-/** Hint that shows this is a session update envelope */
-public interface SessionUpdate {}
diff --git a/sentry-core/src/main/java/io/sentry/core/hints/SessionUpdateHint.java b/sentry-core/src/main/java/io/sentry/core/hints/SessionUpdateHint.java
deleted file mode 100644
index fca36f278..000000000
--- a/sentry-core/src/main/java/io/sentry/core/hints/SessionUpdateHint.java
+++ /dev/null
@@ -1,3 +0,0 @@
-package io.sentry.core.hints;
-
-public final class SessionUpdateHint implements SessionUpdate {}
diff --git a/sentry-core/src/main/java/io/sentry/core/transport/NoOpEventCache.java b/sentry-core/src/main/java/io/sentry/core/transport/NoOpEventCache.java
deleted file mode 100644
index 83a5cd265..000000000
--- a/sentry-core/src/main/java/io/sentry/core/transport/NoOpEventCache.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package io.sentry.core.transport;
-
-import io.sentry.core.SentryEvent;
-import io.sentry.core.cache.IEventCache;
-import java.util.ArrayList;
-import java.util.Iterator;
-import org.jetbrains.annotations.NotNull;
-
-public final class NoOpEventCache implements IEventCache {
- private static final NoOpEventCache instance = new NoOpEventCache();
-
- public static NoOpEventCache getInstance() {
- return instance;
- }
-
- private NoOpEventCache() {}
-
- @Override
- public void store(SentryEvent event) {}
-
- @Override
- public void discard(SentryEvent event) {}
-
- @Override
- public @NotNull Iterator iterator() {
- return new ArrayList(0).iterator();
- }
-}
diff --git a/sentry-core/src/test/java/io/sentry/core/CachedEvent.kt b/sentry-core/src/test/java/io/sentry/core/CachedEvent.kt
deleted file mode 100644
index 6531f40f4..000000000
--- a/sentry-core/src/test/java/io/sentry/core/CachedEvent.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package io.sentry.core
-
-import io.sentry.core.hints.Cached
-
-class CachedEvent : Cached
diff --git a/sentry-core/src/test/java/io/sentry/core/cache/DiskCacheTest.kt b/sentry-core/src/test/java/io/sentry/core/cache/DiskCacheTest.kt
deleted file mode 100644
index d79e47b2a..000000000
--- a/sentry-core/src/test/java/io/sentry/core/cache/DiskCacheTest.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-package io.sentry.core.cache
-
-import com.nhaarman.mockitokotlin2.any
-import com.nhaarman.mockitokotlin2.doAnswer
-import com.nhaarman.mockitokotlin2.mock
-import com.nhaarman.mockitokotlin2.whenever
-import io.sentry.core.ISerializer
-import io.sentry.core.SentryEvent
-import io.sentry.core.SentryOptions
-import io.sentry.core.protocol.SentryId
-import java.io.Reader
-import java.io.Writer
-import java.nio.file.Files
-import java.nio.file.Path
-import kotlin.test.AfterTest
-import kotlin.test.Test
-import kotlin.test.assertEquals
-
-class DiskCacheTest {
- private class Fixture {
- val maxSize = 5
- val dir: Path = Files.createTempDirectory("sentry-disk-cache-test")
-
- fun getSUT(): DiskCache {
- val options = SentryOptions()
- options.cacheDirSize = maxSize
- options.cacheDirPath = dir.toAbsolutePath().toFile().absolutePath
-
- val serializer = mock()
- doAnswer {
- val event = it.arguments[0] as SentryEvent
- val writer = it.arguments[1] as Writer
-
- writer.write(event.eventId.toString())
- }.whenever(serializer).serialize(any(), any())
-
- whenever(serializer.deserializeEvent(any())).thenAnswer {
- val reader = it.arguments[0] as Reader
-
- val ret = SentryEvent()
- val text = reader.readText()
- ret.eventId = SentryId(text)
- ret
- }
-
- options.setSerializer(serializer)
-
- return DiskCache(options)
- }
- }
-
- private val fixture = Fixture()
-
- @AfterTest
- fun cleanUp() {
- fixture.dir.toFile().listFiles()?.forEach { it.delete() }
- Files.delete(fixture.dir)
- }
-
- @Test
- fun `stores events`() {
- val cache = fixture.getSUT()
-
- val nofFiles = { fixture.dir.toFile().list()?.size }
-
- assertEquals(0, nofFiles())
-
- cache.store(SentryEvent())
-
- assertEquals(1, nofFiles())
- }
-
- @Test
- fun `limits the number of stored events`() {
- val cache = fixture.getSUT()
-
- val nofFiles = { fixture.dir.toFile().list()?.size }
-
- assertEquals(0, nofFiles())
-
- (1..fixture.maxSize + 1).forEach { _ ->
- cache.store(SentryEvent(Exception()))
- }
-
- assertEquals(fixture.maxSize, nofFiles())
- }
-
- @Test
- fun `tolerates discarding unknown event`() {
- val cache = fixture.getSUT()
-
- cache.discard(SentryEvent())
-
- // no exception thrown
- }
-
- @Test
- fun `reads the event back`() {
-
- val cache = fixture.getSUT()
-
- val event = SentryEvent()
-
- cache.store(event)
-
- val readEvents = cache.toList()
-
- assertEquals(1, readEvents.size)
-
- val readEvent = readEvents[0]
-
- assertEquals(event.eventId.toString(), readEvent.eventId.toString())
- }
-}
diff --git a/sentry-log4j2/build.gradle.kts b/sentry-log4j2/build.gradle.kts
new file mode 100644
index 000000000..00090d222
--- /dev/null
+++ b/sentry-log4j2/build.gradle.kts
@@ -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 {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+tasks.withType().configureEach {
+ kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
+}
+
+dependencies {
+ api(project(":sentry"))
+ 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(project(":sentry-test-support"))
+ testImplementation(kotlin(Config.kotlinStdLib))
+ testImplementation(Config.TestLibs.kotlinTestJunit)
+ testImplementation(Config.TestLibs.mockitoKotlin)
+ testImplementation(Config.TestLibs.awaitility)
+}
+
+configure {
+ test {
+ java.srcDir("src/test/java")
+ }
+}
+
+jacoco {
+ toolVersion = Config.QualityPlugins.Jacoco.version
+}
+
+tasks.jacocoTestReport {
+ reports {
+ xml.isEnabled = true
+ html.isEnabled = false
+ }
+}
+
+tasks {
+ jacocoTestCoverageVerification {
+ violationRules {
+ rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } }
+ }
+ }
+ 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().configureEach {
+ dependsOn(generateBuildConfig)
+}
+
+// TODO: move these blocks to parent gradle file, DRY
+configure {
+ 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
+}
diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java
new file mode 100644
index 000000000..44da41561
--- /dev/null
+++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java
@@ -0,0 +1,213 @@
+package io.sentry.log4j2;
+
+import io.sentry.Breadcrumb;
+import io.sentry.DateUtils;
+import io.sentry.HubAdapter;
+import io.sentry.IHub;
+import io.sentry.Sentry;
+import io.sentry.SentryEvent;
+import io.sentry.SentryLevel;
+import io.sentry.SentryOptions;
+import io.sentry.protocol.Message;
+import io.sentry.protocol.SdkVersion;
+import io.sentry.transport.ITransport;
+import io.sentry.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 contextData =
+ CollectionUtils.shallowCopy(loggingEvent.getContextData().toMap());
+ if (!contextData.isEmpty()) {
+ event.getContexts().put("Context Data", contextData);
+ }
+
+ return event;
+ }
+
+ private @NotNull List 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;
+ }
+}
diff --git a/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt
new file mode 100644
index 000000000..50ab78fa2
--- /dev/null
+++ b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt
@@ -0,0 +1,304 @@
+package io.sentry.log4j2
+
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.verify
+import io.sentry.HubAdapter
+import io.sentry.SentryLevel
+import io.sentry.test.checkEvent
+import io.sentry.transport.ITransport
+import java.time.Instant
+import java.time.LocalDateTime
+import java.time.ZoneId
+import kotlin.test.AfterTest
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+import org.apache.logging.log4j.Level
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.ThreadContext
+import org.apache.logging.log4j.core.LoggerContext
+import org.apache.logging.log4j.core.config.AppenderRef
+import org.apache.logging.log4j.core.config.Configuration
+import org.apache.logging.log4j.core.config.LoggerConfig
+import org.apache.logging.log4j.spi.ExtendedLogger
+import org.awaitility.kotlin.await
+
+class SentryAppenderTest {
+ private class Fixture {
+ val transport = mock()
+ val loggerContext = LogManager.getContext() as LoggerContext
+ var minimumBreadcrumbLevel: Level? = null
+ var minimumEventLevel: Level? = null
+
+ fun getSut(): ExtendedLogger {
+ loggerContext.start()
+ val config: Configuration = loggerContext.configuration
+ val appender = SentryAppender("sentry", null, "http://key@localhost/proj", minimumBreadcrumbLevel, minimumEventLevel, transport, HubAdapter.getInstance())
+ config.addAppender(appender)
+
+ val ref = AppenderRef.createAppenderRef("sentry", null, null)
+
+ val loggerConfig = LoggerConfig.createLogger(false, Level.TRACE, "sentry_logger", "true", arrayOf(ref), null, config, null)
+ loggerConfig.addAppender(appender, null, null)
+ config.addLogger(SentryAppenderTest::class.java.name, loggerConfig)
+
+ loggerContext.updateLoggers(config)
+
+ appender.start()
+ loggerContext.start()
+
+ return LogManager.getContext().getLogger(SentryAppenderTest::class.java.name)
+ }
+ }
+
+ private var fixture = Fixture()
+
+ @AfterTest
+ fun `stop log4j2`() {
+ fixture.loggerContext.stop()
+ }
+
+ @BeforeTest
+ fun `clear MDC`() {
+ ThreadContext.clearAll()
+ }
+
+ @Test
+ fun `converts message`() {
+ fixture.minimumEventLevel = Level.DEBUG
+ val logger = fixture.getSut()
+ logger.debug("testing message conversion {}, {}", 1, 2)
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals("testing message conversion 1, 2", event.message.formatted)
+ assertEquals("testing message conversion {}, {}", event.message.message)
+ assertEquals(listOf("1", "2"), event.message.params)
+ assertEquals("io.sentry.log4j2.SentryAppenderTest", event.logger)
+ })
+ }
+ }
+
+ @Test
+ fun `event date is in UTC`() {
+ fixture.minimumEventLevel = Level.DEBUG
+ val logger = fixture.getSut()
+ val utcTime = LocalDateTime.now(ZoneId.of("UTC"))
+
+ logger.debug("testing event date")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ val eventTime = Instant.ofEpochMilli(event.timestamp.time)
+ .atZone(ZoneId.systemDefault())
+ .toLocalDateTime()
+
+ assertTrue { eventTime.plusSeconds(1).isAfter(utcTime) }
+ assertTrue { eventTime.minusSeconds(1).isBefore(utcTime) }
+ })
+ }
+ }
+
+ @Test
+ fun `converts trace log level to Sentry level`() {
+ fixture.minimumEventLevel = Level.TRACE
+ val logger = fixture.getSut()
+ logger.trace("testing trace level")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(SentryLevel.DEBUG, event.level)
+ })
+ }
+ }
+
+ @Test
+ fun `converts debug log level to Sentry level`() {
+ fixture.minimumEventLevel = Level.DEBUG
+ val logger = fixture.getSut()
+ logger.debug("testing debug level")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(SentryLevel.DEBUG, event.level)
+ })
+ }
+ }
+
+ @Test
+ fun `converts info log level to Sentry level`() {
+ fixture.minimumEventLevel = Level.INFO
+ val logger = fixture.getSut()
+ logger.info("testing info level")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(SentryLevel.INFO, event.level)
+ })
+ }
+ }
+
+ @Test
+ fun `converts warn log level to Sentry level`() {
+ fixture.minimumEventLevel = Level.WARN
+ val logger = fixture.getSut()
+ logger.warn("testing warn level")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(SentryLevel.WARNING, event.level)
+ })
+ }
+ }
+
+ @Test
+ fun `converts error log level to Sentry level`() {
+ fixture.minimumEventLevel = Level.ERROR
+ val logger = fixture.getSut()
+ logger.error("testing error level")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(SentryLevel.ERROR, event.level)
+ })
+ }
+ }
+
+ @Test
+ fun `converts fatal log level to Sentry level`() {
+ fixture.minimumEventLevel = Level.FATAL
+ val logger = fixture.getSut()
+ logger.fatal("testing fatal level")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(SentryLevel.FATAL, event.level)
+ })
+ }
+ }
+
+ @Test
+ fun `attaches thread information`() {
+ fixture.minimumEventLevel = Level.WARN
+ val logger = fixture.getSut()
+ logger.warn("testing thread information")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertNotNull(event.getExtra("thread_name"))
+ })
+ }
+ }
+
+ @Test
+ fun `sets tags from ThreadContext`() {
+ fixture.minimumEventLevel = Level.WARN
+ val logger = fixture.getSut()
+ ThreadContext.put("key", "value")
+ logger.warn("testing MDC tags")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(mapOf("key" to "value"), event.contexts["Context Data"])
+ })
+ }
+ }
+
+ @Test
+ fun `does not create MDC context when no MDC tags are set`() {
+ fixture.minimumEventLevel = Level.WARN
+ val logger = fixture.getSut()
+ logger.warn("testing without MDC tags")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertFalse(event.contexts.containsKey("MDC"))
+ })
+ }
+ }
+
+ @Test
+ fun `sets SDK version`() {
+ fixture.minimumEventLevel = Level.INFO
+ val logger = fixture.getSut()
+ logger.info("testing sdk version")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(BuildConfig.SENTRY_LOG4J2_SDK_NAME, event.sdk.name)
+ assertEquals(BuildConfig.VERSION_NAME, event.sdk.version)
+ assertNotNull(event.sdk.packages)
+ assertTrue(event.sdk.packages!!.any { pkg ->
+ "maven:sentry-log4j2" == pkg.name &&
+ BuildConfig.VERSION_NAME == pkg.version
+ })
+ })
+ }
+ }
+
+ @Test
+ fun `attaches breadcrumbs with level higher than minimumBreadcrumbLevel`() {
+ fixture.minimumEventLevel = Level.WARN
+ fixture.minimumBreadcrumbLevel = Level.DEBUG
+ val logger = fixture.getSut()
+ val utcTime = LocalDateTime.now(ZoneId.of("UTC"))
+
+ logger.debug("this should be a breadcrumb #1")
+ logger.info("this should be a breadcrumb #2")
+ logger.warn("testing message with breadcrumbs")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(2, event.breadcrumbs.size)
+ val breadcrumb = event.breadcrumbs[0]
+ val breadcrumbTime = Instant.ofEpochMilli(event.timestamp.time)
+ .atZone(ZoneId.systemDefault())
+ .toLocalDateTime()
+ assertTrue { breadcrumbTime.plusSeconds(1).isAfter(utcTime) }
+ assertTrue { breadcrumbTime.minusSeconds(1).isBefore(utcTime) }
+ assertEquals("this should be a breadcrumb #1", breadcrumb.message)
+ assertEquals("io.sentry.log4j2.SentryAppenderTest", breadcrumb.category)
+ assertEquals(SentryLevel.DEBUG, breadcrumb.level)
+ })
+ }
+ }
+
+ @Test
+ fun `does not attach breadcrumbs with level lower than minimumBreadcrumbLevel`() {
+ fixture.minimumEventLevel = Level.WARN
+ fixture.minimumBreadcrumbLevel = Level.INFO
+ val logger = fixture.getSut()
+
+ logger.debug("this should NOT be a breadcrumb")
+ logger.info("this should be a breadcrumb")
+ logger.warn("testing message with breadcrumbs")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(1, event.breadcrumbs.size)
+ assertEquals("this should be a breadcrumb", event.breadcrumbs[0].message)
+ })
+ }
+ }
+
+ @Test
+ fun `attaches breadcrumbs for default appender configuration`() {
+ val logger = fixture.getSut()
+
+ logger.debug("this should not be a breadcrumb as the level is lower than the minimum INFO")
+ logger.info("this should be a breadcrumb")
+ logger.warn("this should not be sent as the event but be a breadcrumb")
+ logger.error("this should be sent as the event")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(2, event.breadcrumbs.size)
+ assertEquals("this should be a breadcrumb", event.breadcrumbs[0].message)
+ assertEquals("this should not be sent as the event but be a breadcrumb", event.breadcrumbs[1].message)
+ })
+ }
+ }
+}
diff --git a/sentry-core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/sentry-log4j2/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
similarity index 100%
rename from sentry-core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
rename to sentry-log4j2/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
diff --git a/sentry-logback/build.gradle.kts b/sentry-logback/build.gradle.kts
new file mode 100644
index 000000000..0209b1ca6
--- /dev/null
+++ b/sentry-logback/build.gradle.kts
@@ -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 {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+tasks.withType().configureEach {
+ kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
+}
+
+dependencies {
+ api(project(":sentry"))
+ 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(project(":sentry-test-support"))
+ testImplementation(kotlin(Config.kotlinStdLib))
+ testImplementation(Config.TestLibs.kotlinTestJunit)
+ testImplementation(Config.TestLibs.mockitoKotlin)
+ testImplementation(Config.TestLibs.awaitility)
+}
+
+configure {
+ test {
+ java.srcDir("src/test/java")
+ }
+}
+
+jacoco {
+ toolVersion = Config.QualityPlugins.Jacoco.version
+}
+
+tasks.jacocoTestReport {
+ reports {
+ xml.isEnabled = true
+ html.isEnabled = false
+ }
+}
+
+tasks {
+ jacocoTestCoverageVerification {
+ violationRules {
+ rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } }
+ }
+ }
+ 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().configureEach {
+ dependsOn(generateBuildConfig)
+}
+
+// TODO: move these blocks to parent gradle file, DRY
+configure {
+ 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
+}
diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java
new file mode 100644
index 000000000..b9d4a5134
--- /dev/null
+++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java
@@ -0,0 +1,172 @@
+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.Breadcrumb;
+import io.sentry.DateUtils;
+import io.sentry.Sentry;
+import io.sentry.SentryEvent;
+import io.sentry.SentryLevel;
+import io.sentry.SentryOptions;
+import io.sentry.protocol.Message;
+import io.sentry.protocol.SdkVersion;
+import io.sentry.transport.ITransport;
+import io.sentry.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.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 {
+ private @Nullable SentryOptions options;
+ private @Nullable ITransport transport;
+ private @NotNull Level minimumBreadcrumbLevel = Level.INFO;
+ private @NotNull Level minimumEventLevel = Level.ERROR;
+
+ @Override
+ public void start() {
+ if (options != null && options.getDsn() != null) {
+ options.setSentryClientName(BuildConfig.SENTRY_LOGBACK_SDK_NAME);
+ options.setSdkVersion(createSdkVersion(options));
+ Optional.ofNullable(transport).ifPresent(options::setTransport);
+ Sentry.init(options);
+ }
+ super.start();
+ }
+
+ @Override
+ protected void append(@NotNull ILoggingEvent eventObject) {
+ if (eventObject.getLevel().isGreaterOrEqual(minimumEventLevel)) {
+ Sentry.captureEvent(createEvent(eventObject));
+ }
+ if (eventObject.getLevel().isGreaterOrEqual(minimumBreadcrumbLevel)) {
+ Sentry.addBreadcrumb(createBreadcrumb(eventObject));
+ }
+ }
+
+ /**
+ * Creates {@link SentryEvent} from Logback's {@link ILoggingEvent}.
+ *
+ * @param loggingEvent the logback event
+ * @return the sentry event
+ */
+ // for the Android compatibility we must use old Java Date class
+ @SuppressWarnings("JdkObsolete")
+ 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());
+ 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) {
+ event.setExtra("thread_name", loggingEvent.getThreadName());
+ }
+
+ final Map mdcProperties =
+ CollectionUtils.shallowCopy(loggingEvent.getMDCPropertyMap());
+ if (!mdcProperties.isEmpty()) {
+ event.getContexts().put("MDC", mdcProperties);
+ }
+
+ return event;
+ }
+
+ private @NotNull List toParams(@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 Logback's {@link ILoggingEvent}.
+ *
+ * @param loggingEvent the logback event
+ * @return the sentry breadcrumb
+ */
+ private @NotNull Breadcrumb createBreadcrumb(final @NotNull ILoggingEvent loggingEvent) {
+ final Breadcrumb breadcrumb = new Breadcrumb();
+ breadcrumb.setLevel(formatLevel(loggingEvent.getLevel()));
+ breadcrumb.setCategory(loggingEvent.getLoggerName());
+ breadcrumb.setMessage(loggingEvent.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(@NotNull Level level) {
+ if (level.isGreaterOrEqual(Level.ERROR)) {
+ 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);
+
+ return sdkVersion;
+ }
+
+ public void setOptions(final @Nullable SentryOptions options) {
+ this.options = options;
+ }
+
+ public void setMinimumBreadcrumbLevel(final @Nullable Level minimumBreadcrumbLevel) {
+ if (minimumBreadcrumbLevel != null) {
+ this.minimumBreadcrumbLevel = minimumBreadcrumbLevel;
+ }
+ }
+
+ public void setMinimumEventLevel(final @Nullable Level minimumEventLevel) {
+ if (minimumEventLevel != null) {
+ this.minimumEventLevel = minimumEventLevel;
+ }
+ }
+
+ @ApiStatus.Internal
+ void setTransport(@Nullable ITransport transport) {
+ this.transport = transport;
+ }
+}
diff --git a/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt b/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt
new file mode 100644
index 000000000..7def3e5bd
--- /dev/null
+++ b/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt
@@ -0,0 +1,272 @@
+package io.sentry.logback
+
+import ch.qos.logback.classic.Level
+import ch.qos.logback.classic.LoggerContext
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.verify
+import com.nhaarman.mockitokotlin2.whenever
+import io.sentry.SentryLevel
+import io.sentry.SentryOptions
+import io.sentry.test.checkEvent
+import io.sentry.transport.ITransport
+import io.sentry.transport.TransportResult
+import java.time.Instant
+import java.time.LocalDateTime
+import java.time.ZoneId
+import kotlin.test.AfterTest
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+import org.awaitility.kotlin.await
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.slf4j.MDC
+
+class SentryAppenderTest {
+ private class Fixture(minimumBreadcrumbLevel: Level? = null, minimumEventLevel: Level? = null) {
+ val transport = mock()
+ val logger: Logger = LoggerFactory.getLogger(SentryAppenderTest::class.java)
+ val loggerContext = LoggerFactory.getILoggerFactory() as LoggerContext
+
+ init {
+ whenever(transport.send(any())).thenReturn(TransportResult.success())
+ val appender = SentryAppender()
+ val options = SentryOptions()
+ options.dsn = "http://key@localhost/proj"
+ appender.setOptions(options)
+ appender.setMinimumBreadcrumbLevel(minimumBreadcrumbLevel)
+ appender.setMinimumEventLevel(minimumEventLevel)
+ appender.context = loggerContext
+ appender.setTransport(transport)
+ val rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME)
+ rootLogger.level = Level.TRACE
+ rootLogger.addAppender(appender)
+ appender.start()
+ loggerContext.start()
+ }
+ }
+
+ private lateinit var fixture: Fixture
+
+ @AfterTest
+ fun `stop logback`() {
+ fixture.loggerContext.stop()
+ }
+
+ @BeforeTest
+ fun `clear MDC`() {
+ MDC.clear()
+ }
+
+ @Test
+ fun `converts message`() {
+ fixture = Fixture(minimumEventLevel = Level.DEBUG)
+ fixture.logger.debug("testing message conversion {}, {}", 1, 2)
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals("testing message conversion 1, 2", event.message.formatted)
+ assertEquals("testing message conversion {}, {}", event.message.message)
+ assertEquals(listOf("1", "2"), event.message.params)
+ assertEquals("io.sentry.logback.SentryAppenderTest", event.logger)
+ })
+ }
+ }
+
+ @Test
+ fun `event date is in UTC`() {
+ fixture = Fixture(minimumEventLevel = Level.DEBUG)
+ val utcTime = LocalDateTime.now(ZoneId.of("UTC"))
+
+ fixture.logger.debug("testing event date")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ val eventTime = Instant.ofEpochMilli(event.timestamp.time)
+ .atZone(ZoneId.systemDefault())
+ .toLocalDateTime()
+
+ assertTrue { eventTime.plusSeconds(1).isAfter(utcTime) }
+ assertTrue { eventTime.minusSeconds(1).isBefore(utcTime) }
+ })
+ }
+ }
+
+ @Test
+ fun `converts trace log level to Sentry level`() {
+ fixture = Fixture(minimumEventLevel = Level.TRACE)
+ fixture.logger.trace("testing trace level")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(SentryLevel.DEBUG, event.level)
+ })
+ }
+ }
+
+ @Test
+ fun `converts debug log level to Sentry level`() {
+ fixture = Fixture(minimumEventLevel = Level.DEBUG)
+ fixture.logger.debug("testing debug level")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(SentryLevel.DEBUG, event.level)
+ })
+ }
+ }
+
+ @Test
+ fun `converts info log level to Sentry level`() {
+ fixture = Fixture(minimumEventLevel = Level.INFO)
+ fixture.logger.info("testing info level")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(SentryLevel.INFO, event.level)
+ })
+ }
+ }
+
+ @Test
+ fun `converts warn log level to Sentry level`() {
+ fixture = Fixture(minimumEventLevel = Level.WARN)
+ fixture.logger.warn("testing warn level")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(SentryLevel.WARNING, event.level)
+ })
+ }
+ }
+
+ @Test
+ fun `converts error log level to Sentry level`() {
+ fixture = Fixture(minimumEventLevel = Level.ERROR)
+ fixture.logger.error("testing error level")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(SentryLevel.ERROR, event.level)
+ })
+ }
+ }
+
+ @Test
+ fun `attaches thread information`() {
+ fixture = Fixture(minimumEventLevel = Level.WARN)
+ fixture.logger.warn("testing thread information")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertNotNull(event.getExtra("thread_name"))
+ })
+ }
+ }
+
+ @Test
+ fun `sets tags from MDC`() {
+ fixture = Fixture(minimumEventLevel = Level.WARN)
+ MDC.put("key", "value")
+ fixture.logger.warn("testing MDC tags")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(mapOf("key" to "value"), event.contexts["MDC"])
+ })
+ }
+ }
+
+ @Test
+ fun `does not create MDC context when no MDC tags are set`() {
+ fixture = Fixture(minimumEventLevel = Level.WARN)
+ fixture.logger.warn("testing without MDC tags")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertFalse(event.contexts.containsKey("MDC"))
+ })
+ }
+ }
+
+ @Test
+ fun `sets SDK version`() {
+ fixture = Fixture(minimumEventLevel = Level.INFO)
+ fixture.logger.info("testing sdk version")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(BuildConfig.SENTRY_LOGBACK_SDK_NAME, event.sdk.name)
+ assertEquals(BuildConfig.VERSION_NAME, event.sdk.version)
+ assertNotNull(event.sdk.packages)
+ assertTrue(event.sdk.packages!!.any { pkg ->
+ "maven:sentry-logback" == pkg.name &&
+ BuildConfig.VERSION_NAME == pkg.version
+ })
+ })
+ }
+ }
+
+ @Test
+ fun `attaches breadcrumbs with level higher than minimumBreadcrumbLevel`() {
+ fixture = Fixture(minimumBreadcrumbLevel = Level.DEBUG, minimumEventLevel = Level.WARN)
+ val utcTime = LocalDateTime.now(ZoneId.of("UTC"))
+
+ fixture.logger.debug("this should be a breadcrumb #1")
+ fixture.logger.info("this should be a breadcrumb #2")
+ fixture.logger.warn("testing message with breadcrumbs")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(2, event.breadcrumbs.size)
+ val breadcrumb = event.breadcrumbs[0]
+ val breadcrumbTime = Instant.ofEpochMilli(event.timestamp.time)
+ .atZone(ZoneId.systemDefault())
+ .toLocalDateTime()
+ assertTrue { breadcrumbTime.plusSeconds(1).isAfter(utcTime) }
+ assertTrue { breadcrumbTime.minusSeconds(1).isBefore(utcTime) }
+ assertEquals("this should be a breadcrumb #1", breadcrumb.message)
+ assertEquals("io.sentry.logback.SentryAppenderTest", breadcrumb.category)
+ assertEquals(SentryLevel.DEBUG, breadcrumb.level)
+ })
+ }
+ }
+
+ @Test
+ fun `does not attach breadcrumbs with level lower than minimumBreadcrumbLevel`() {
+ fixture = Fixture(minimumBreadcrumbLevel = Level.INFO, minimumEventLevel = Level.WARN)
+
+ fixture.logger.debug("this should NOT be a breadcrumb")
+ fixture.logger.info("this should be a breadcrumb")
+ fixture.logger.warn("testing message with breadcrumbs")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(1, event.breadcrumbs.size)
+ assertEquals("this should be a breadcrumb", event.breadcrumbs[0].message)
+ })
+ }
+ }
+
+ @Test
+ fun `attaches breadcrumbs for default appender configuration`() {
+ fixture = Fixture()
+
+ fixture.logger.debug("this should not be a breadcrumb as the level is lower than the minimum INFO")
+ fixture.logger.info("this should be a breadcrumb")
+ fixture.logger.warn("this should not be sent as the event but be a breadcrumb")
+ fixture.logger.error("this should be sent as the event")
+
+ await.untilAsserted {
+ verify(fixture.transport).send(checkEvent { event ->
+ assertEquals(2, event.breadcrumbs.size)
+ assertEquals("this should be a breadcrumb", event.breadcrumbs[0].message)
+ assertEquals("this should not be sent as the event but be a breadcrumb", event.breadcrumbs[1].message)
+ })
+ }
+ }
+}
diff --git a/sentry-logback/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/sentry-logback/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 000000000..1f0955d45
--- /dev/null
+++ b/sentry-logback/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/sentry-samples/sentry-samples-android/build.gradle.kts b/sentry-samples/sentry-samples-android/build.gradle.kts
index 5c293457a..fd3b53bae 100644
--- a/sentry-samples/sentry-samples-android/build.gradle.kts
+++ b/sentry-samples/sentry-samples-android/build.gradle.kts
@@ -66,7 +66,8 @@ android {
buildTypes {
getByName("debug") {
manifestPlaceholders = mapOf(
- "sentryDebug" to true
+ "sentryDebug" to true,
+ "sentryEnvironment" to "debug"
)
}
getByName("release") {
@@ -76,7 +77,8 @@ android {
isShrinkResources = true
manifestPlaceholders = mapOf(
- "sentryDebug" to false
+ "sentryDebug" to false,
+ "sentryEnvironment" to "release"
)
}
}
diff --git a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml
index 8920c9db0..6caf842bb 100644
--- a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml
+++ b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml
@@ -57,13 +57,13 @@
-
+
-
-
+
+
-
+
diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/sample/MainActivity.java b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/sample/MainActivity.java
index 9a9b367a4..07f21b628 100644
--- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/sample/MainActivity.java
+++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/sample/MainActivity.java
@@ -2,8 +2,8 @@
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
-import io.sentry.core.Sentry;
-import io.sentry.core.protocol.User;
+import io.sentry.Sentry;
+import io.sentry.protocol.User;
import io.sentry.sample.databinding.ActivityMainBinding;
import java.util.Collections;
diff --git a/sentry-samples/sentry-samples-console/build.gradle.kts b/sentry-samples/sentry-samples-console/build.gradle.kts
index 496bd3052..44f48604a 100644
--- a/sentry-samples/sentry-samples-console/build.gradle.kts
+++ b/sentry-samples/sentry-samples-console/build.gradle.kts
@@ -9,5 +9,5 @@ configure {
}
dependencies {
- implementation(project(":sentry-core"))
+ implementation(project(":sentry"))
}
diff --git a/sentry-samples/sentry-samples-console/src/main/java/io/sentry/samples/console/Main.java b/sentry-samples/sentry-samples-console/src/main/java/io/sentry/samples/console/Main.java
index 2ecd50a29..ae9b2688a 100644
--- a/sentry-samples/sentry-samples-console/src/main/java/io/sentry/samples/console/Main.java
+++ b/sentry-samples/sentry-samples-console/src/main/java/io/sentry/samples/console/Main.java
@@ -1,12 +1,12 @@
package io.sentry.samples.console;
-import io.sentry.core.Breadcrumb;
-import io.sentry.core.EventProcessor;
-import io.sentry.core.Sentry;
-import io.sentry.core.SentryEvent;
-import io.sentry.core.SentryLevel;
-import io.sentry.core.protocol.Message;
-import io.sentry.core.protocol.User;
+import io.sentry.Breadcrumb;
+import io.sentry.EventProcessor;
+import io.sentry.Sentry;
+import io.sentry.SentryEvent;
+import io.sentry.SentryLevel;
+import io.sentry.protocol.Message;
+import io.sentry.protocol.User;
import java.util.Collections;
public class Main {
diff --git a/sentry-samples/sentry-samples-log4j2/build.gradle.kts b/sentry-samples/sentry-samples-log4j2/build.gradle.kts
new file mode 100644
index 000000000..69785a28e
--- /dev/null
+++ b/sentry-samples/sentry-samples-log4j2/build.gradle.kts
@@ -0,0 +1,14 @@
+plugins {
+ java
+ id(Config.QualityPlugins.gradleVersions)
+}
+
+configure {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+dependencies {
+ implementation(project(":sentry-log4j2"))
+ implementation(Config.Libs.log4j2Api)
+}
diff --git a/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java b/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java
new file mode 100644
index 000000000..6f1a4b79c
--- /dev/null
+++ b/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java
@@ -0,0 +1,30 @@
+package io.sentry.samples.log4j2;
+
+import java.util.UUID;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+
+public class Main {
+ private static final Logger LOGGER = LogManager.getLogger(Main.class);
+
+ public static void main(String[] args) {
+ // The SDK was initialized through the appender configuration because a DSN was set there.
+ // Update the DSN in log4j2.xml to see these events in your Sentry dashboard.
+ LOGGER.debug("Hello Sentry!");
+
+ // ThreadContext parameters are converted to Sentry Event tags
+ ThreadContext.put("userId", UUID.randomUUID().toString());
+
+ // logging arguments are converted to Sentry Event parameters
+ LOGGER.info("User has made a purchase of product: {}", 445);
+ // because minimumEventLevel is set to WARN this raises an event
+ LOGGER.warn("Important warning");
+
+ try {
+ throw new RuntimeException("Invalid productId=445");
+ } catch (Exception e) {
+ LOGGER.error("Something went wrong", e);
+ }
+ }
+}
diff --git a/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml b/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml
new file mode 100644
index 000000000..b7df67f12
--- /dev/null
+++ b/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sentry-samples/sentry-samples-logback/build.gradle.kts b/sentry-samples/sentry-samples-logback/build.gradle.kts
new file mode 100644
index 000000000..44841bc56
--- /dev/null
+++ b/sentry-samples/sentry-samples-logback/build.gradle.kts
@@ -0,0 +1,14 @@
+plugins {
+ java
+ id(Config.QualityPlugins.gradleVersions)
+}
+
+configure {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+dependencies {
+ implementation(project(":sentry-logback"))
+ implementation(Config.Libs.logbackClassic)
+}
diff --git a/sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/logback/Main.java b/sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/logback/Main.java
new file mode 100644
index 000000000..3de86d711
--- /dev/null
+++ b/sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/logback/Main.java
@@ -0,0 +1,26 @@
+package io.sentry.samples.logback;
+
+import java.util.UUID;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+
+public class Main {
+ private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
+
+ public static void main(String[] args) {
+ LOGGER.debug("Hello Sentry!");
+
+ // MDC parameters are converted to Sentry Event tags
+ MDC.put("userId", UUID.randomUUID().toString());
+
+ // logging arguments are converted to Sentry Event parameters
+ LOGGER.info("User has made a purchase of product: {}", 445);
+
+ try {
+ throw new RuntimeException("Invalid productId=445");
+ } catch (Exception e) {
+ LOGGER.error("Something went wrong", e);
+ }
+ }
+}
diff --git a/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml b/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml
new file mode 100644
index 000000000..76de7f2f3
--- /dev/null
+++ b/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+ https://f7f320d5c3a54709be7b28e0f2ca7081@sentry.io/1808954
+
+
+
+ WARN
+
+ DEBUG
+
+
+
+
+
+
+
diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts
new file mode 100644
index 000000000..e342bf150
--- /dev/null
+++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts
@@ -0,0 +1,41 @@
+import org.jetbrains.kotlin.config.KotlinCompilerVersion
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+ id(Config.BuildPlugins.springBoot) version Config.springBootVersion
+ id(Config.BuildPlugins.springDependencyManagement) version Config.BuildPlugins.springDependencyManagementVersion
+ kotlin("jvm")
+ kotlin("plugin.spring") version Config.kotlinVersion
+}
+
+group = "io.sentry.sample.spring-boot"
+version = "0.0.1-SNAPSHOT"
+java.sourceCompatibility = JavaVersion.VERSION_1_8
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation(Config.Libs.springBootStarterSecurity)
+ implementation(Config.Libs.springBootStarterWeb)
+ implementation(Config.Libs.springBootStarter)
+ implementation("org.jetbrains.kotlin:kotlin-reflect")
+ implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION))
+ implementation(project(":sentry-spring-boot-starter"))
+ implementation(project(":sentry-logback"))
+ testImplementation(Config.Libs.springBootStarterTest) {
+ exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
+ }
+}
+
+tasks.withType {
+ useJUnitPlatform()
+}
+
+tasks.withType {
+ kotlinOptions {
+ freeCompilerArgs = listOf("-Xjsr305=strict")
+ jvmTarget = JavaVersion.VERSION_1_8.toString()
+ }
+}
diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java
new file mode 100644
index 000000000..1eccfb1a5
--- /dev/null
+++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java
@@ -0,0 +1,35 @@
+package io.sentry.samples.spring.boot;
+
+import io.sentry.EventProcessor;
+import io.sentry.SentryEvent;
+import io.sentry.protocol.SentryRuntime;
+import org.jetbrains.annotations.Nullable;
+import org.springframework.stereotype.Component;
+
+/**
+ * Custom {@link EventProcessor} implementation lets modifying {@link SentryEvent}s before they are
+ * sent to Sentry.
+ */
+@Component
+public class CustomEventProcessor implements EventProcessor {
+ private final String javaVersion;
+ private final String javaVendor;
+
+ public CustomEventProcessor(String javaVersion, String javaVendor) {
+ this.javaVersion = javaVersion;
+ this.javaVendor = javaVendor;
+ }
+
+ public CustomEventProcessor() {
+ this(System.getProperty("java.version"), System.getProperty("java.vendor"));
+ }
+
+ @Override
+ public SentryEvent process(SentryEvent event, @Nullable Object hint) {
+ final SentryRuntime runtime = new SentryRuntime();
+ runtime.setVersion(javaVersion);
+ runtime.setName(javaVendor);
+ event.getContexts().setRuntime(runtime);
+ return event;
+ }
+}
diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/Person.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/Person.java
new file mode 100644
index 000000000..2a2177d46
--- /dev/null
+++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/Person.java
@@ -0,0 +1,24 @@
+package io.sentry.samples.spring.boot;
+
+public class Person {
+ private final String firstName;
+ private final String lastName;
+
+ public Person(String firstName, String lastName) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ @Override
+ public String toString() {
+ return "Person{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + '}';
+ }
+}
diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/PersonController.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/PersonController.java
new file mode 100644
index 000000000..7ee2e4604
--- /dev/null
+++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/PersonController.java
@@ -0,0 +1,28 @@
+package io.sentry.samples.spring.boot;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/person/")
+public class PersonController {
+ private static final Logger LOGGER = LoggerFactory.getLogger(PersonController.class);
+
+ @GetMapping("{id}")
+ Person person(@PathVariable Long id) {
+ LOGGER.info("Loading person with id={}", id);
+ throw new IllegalArgumentException("Something went wrong [id=" + id + "]");
+ }
+
+ @PostMapping
+ Person create(@RequestBody Person person) {
+ LOGGER.warn("Creating person: {}", person);
+ return person;
+ }
+}
diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SecurityConfiguration.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SecurityConfiguration.java
new file mode 100644
index 000000000..bc1c312b0
--- /dev/null
+++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SecurityConfiguration.java
@@ -0,0 +1,40 @@
+package io.sentry.samples.spring.boot;
+
+import org.jetbrains.annotations.NotNull;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+
+@Configuration
+public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ // this API is meant to be consumed by non-browser clients thus the CSRF protection is not needed.
+ @Override
+ @SuppressWarnings("lgtm[java/spring-disabled-csrf-protection]")
+ protected void configure(final @NotNull HttpSecurity http) throws Exception {
+ http.csrf().disable().authorizeRequests().anyRequest().authenticated().and().httpBasic();
+ }
+
+ @Bean
+ @Override
+ public @NotNull UserDetailsService userDetailsService() {
+ final PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
+
+ final UserDetails user =
+ User.builder()
+ .passwordEncoder(encoder::encode)
+ .username("user")
+ .password("password")
+ .roles("USER")
+ .build();
+
+ return new InMemoryUserDetailsManager(user);
+ }
+}
diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SentryDemoApplication.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SentryDemoApplication.java
new file mode 100644
index 000000000..14e57e0cc
--- /dev/null
+++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SentryDemoApplication.java
@@ -0,0 +1,11 @@
+package io.sentry.samples.spring.boot;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SentryDemoApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(SentryDemoApplication.class, args);
+ }
+}
diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties
new file mode 100644
index 000000000..84b7ca93c
--- /dev/null
+++ b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties
@@ -0,0 +1,5 @@
+# NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in your Sentry project/dashboard
+sentry.dsn=https://f7f320d5c3a54709be7b28e0f2ca7081@sentry.io/1808954
+sentry.send-default-pii=true
+# Sentry Spring Boot integration allows more fine-grained SentryOptions configuration
+sentry.max-breadcrumbs=150
diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/resources/logback.xml b/sentry-samples/sentry-samples-spring-boot/src/main/resources/logback.xml
new file mode 100644
index 000000000..26c88f349
--- /dev/null
+++ b/sentry-samples/sentry-samples-spring-boot/src/main/resources/logback.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ WARN
+
+
+
+
+
+
+
diff --git a/sentry-samples/sentry-samples-spring/build.gradle.kts b/sentry-samples/sentry-samples-spring/build.gradle.kts
new file mode 100644
index 000000000..dce35dcd2
--- /dev/null
+++ b/sentry-samples/sentry-samples-spring/build.gradle.kts
@@ -0,0 +1,41 @@
+import org.jetbrains.kotlin.config.KotlinCompilerVersion
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+ id(Config.BuildPlugins.springBoot) version Config.springBootVersion
+ id(Config.BuildPlugins.springDependencyManagement) version Config.BuildPlugins.springDependencyManagementVersion
+ kotlin("jvm")
+ kotlin("plugin.spring") version Config.kotlinVersion
+}
+
+group = "io.sentry.sample.spring"
+version = "0.0.1-SNAPSHOT"
+java.sourceCompatibility = JavaVersion.VERSION_1_8
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation(Config.Libs.springBootStarterSecurity)
+ implementation(Config.Libs.springBootStarterWeb)
+ implementation(Config.Libs.springBootStarter)
+ implementation("org.jetbrains.kotlin:kotlin-reflect")
+ implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION))
+ implementation(project(":sentry-spring"))
+ implementation(project(":sentry-logback"))
+ testImplementation(Config.Libs.springBootStarterTest) {
+ exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
+ }
+}
+
+tasks.withType {
+ useJUnitPlatform()
+}
+
+tasks.withType {
+ kotlinOptions {
+ freeCompilerArgs = listOf("-Xjsr305=strict")
+ jvmTarget = JavaVersion.VERSION_1_8.toString()
+ }
+}
diff --git a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/Person.java b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/Person.java
new file mode 100644
index 000000000..f4588a7f6
--- /dev/null
+++ b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/Person.java
@@ -0,0 +1,24 @@
+package io.sentry.samples.spring;
+
+public class Person {
+ private final String firstName;
+ private final String lastName;
+
+ public Person(String firstName, String lastName) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ @Override
+ public String toString() {
+ return "Person{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + '}';
+ }
+}
diff --git a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/PersonController.java b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/PersonController.java
new file mode 100644
index 000000000..98aa10c0e
--- /dev/null
+++ b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/PersonController.java
@@ -0,0 +1,28 @@
+package io.sentry.samples.spring;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/person/")
+public class PersonController {
+ private static final Logger LOGGER = LoggerFactory.getLogger(PersonController.class);
+
+ @GetMapping("{id}")
+ Person person(@PathVariable Long id) {
+ LOGGER.info("Loading person with id={}", id);
+ throw new IllegalArgumentException("Something went wrong [id=" + id + "]");
+ }
+
+ @PostMapping
+ Person create(@RequestBody Person person) {
+ LOGGER.warn("Creating person: {}", person);
+ return person;
+ }
+}
diff --git a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SecurityConfiguration.java b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SecurityConfiguration.java
new file mode 100644
index 000000000..7f6f6fe31
--- /dev/null
+++ b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SecurityConfiguration.java
@@ -0,0 +1,40 @@
+package io.sentry.samples.spring;
+
+import org.jetbrains.annotations.NotNull;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+
+@Configuration
+public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ // this API is meant to be consumed by non-browser clients thus the CSRF protection is not needed.
+ @Override
+ @SuppressWarnings("lgtm[java/spring-disabled-csrf-protection]")
+ protected void configure(final @NotNull HttpSecurity http) throws Exception {
+ http.csrf().disable().authorizeRequests().anyRequest().authenticated().and().httpBasic();
+ }
+
+ @Bean
+ @Override
+ public @NotNull UserDetailsService userDetailsService() {
+ final PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
+
+ final UserDetails user =
+ User.builder()
+ .passwordEncoder(encoder::encode)
+ .username("user")
+ .password("password")
+ .roles("USER")
+ .build();
+
+ return new InMemoryUserDetailsManager(user);
+ }
+}
diff --git a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java
new file mode 100644
index 000000000..cf87d72aa
--- /dev/null
+++ b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SentryDemoApplication.java
@@ -0,0 +1,17 @@
+package io.sentry.samples.spring;
+
+import io.sentry.spring.EnableSentry;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+// NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in your Sentry
+// project/dashboard
+@EnableSentry(
+ dsn = "https://f7f320d5c3a54709be7b28e0f2ca7081@sentry.io/1808954",
+ sendDefaultPii = true)
+public class SentryDemoApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(SentryDemoApplication.class, args);
+ }
+}
diff --git a/sentry-samples/sentry-samples-spring/src/main/resources/logback.xml b/sentry-samples/sentry-samples-spring/src/main/resources/logback.xml
new file mode 100644
index 000000000..26c88f349
--- /dev/null
+++ b/sentry-samples/sentry-samples-spring/src/main/resources/logback.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ WARN
+
+
+
+
+
+
+
diff --git a/sentry-spring-boot-starter/build.gradle.kts b/sentry-spring-boot-starter/build.gradle.kts
new file mode 100644
index 000000000..5f606c037
--- /dev/null
+++ b/sentry-spring-boot-starter/build.gradle.kts
@@ -0,0 +1,124 @@
+import com.novoda.gradle.release.PublishExtension
+import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import org.springframework.boot.gradle.plugin.SpringBootPlugin
+
+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
+ id(Config.BuildPlugins.springBoot) version Config.springBootVersion apply false
+}
+
+apply(plugin = Config.BuildPlugins.springDependencyManagement)
+
+the().apply {
+ imports {
+ mavenBom(SpringBootPlugin.BOM_COORDINATES)
+ }
+}
+
+configure {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+tasks.withType().configureEach {
+ kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
+ kotlinOptions.languageVersion = Config.springKotlinCompatibleLanguageVersion
+}
+
+dependencies {
+ api(project(":sentry"))
+ api(project(":sentry-spring"))
+ implementation(Config.Libs.springBootStarter)
+ implementation(Config.Libs.springWeb)
+ implementation(Config.Libs.servletApi)
+
+ annotationProcessor(Config.AnnotationProcessors.springBootAutoConfigure)
+ annotationProcessor(Config.AnnotationProcessors.springBootConfiguration)
+
+ compileOnly(Config.CompileOnly.nopen)
+ errorprone(Config.CompileOnly.nopenChecker)
+ errorprone(Config.CompileOnly.errorprone)
+ errorproneJavac(Config.CompileOnly.errorProneJavac8)
+ compileOnly(Config.CompileOnly.jetbrainsAnnotations)
+
+ // tests
+ testImplementation(project(":sentry-test-support"))
+ testImplementation(kotlin(Config.kotlinStdLib))
+ testImplementation(Config.TestLibs.kotlinTestJunit)
+ testImplementation(Config.TestLibs.mockitoKotlin)
+ testImplementation(Config.Libs.springBootStarterTest)
+ testImplementation(Config.Libs.springBootStarterWeb)
+ testImplementation(Config.Libs.springBootStarterSecurity)
+ testImplementation(Config.TestLibs.awaitility)
+}
+
+configure {
+ test {
+ java.srcDir("src/test/java")
+ }
+}
+
+jacoco {
+ toolVersion = Config.QualityPlugins.Jacoco.version
+}
+
+tasks.jacocoTestReport {
+ reports {
+ xml.isEnabled = true
+ html.isEnabled = false
+ }
+}
+
+tasks {
+ jacocoTestCoverageVerification {
+ violationRules {
+ rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } }
+ }
+ }
+ check {
+ dependsOn(jacocoTestCoverageVerification)
+ dependsOn(jacocoTestReport)
+ }
+}
+
+buildConfig {
+ useJavaOutput()
+ packageName("io.sentry.spring.boot")
+ buildConfigField("String", "SENTRY_SPRING_BOOT_SDK_NAME", "\"${Config.Sentry.SENTRY_SPRING_BOOT_SDK_NAME}\"")
+ buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
+}
+
+val generateBuildConfig by tasks
+tasks.withType().configureEach {
+ dependsOn(generateBuildConfig)
+}
+
+// TODO: move these blocks to parent gradle file, DRY
+configure {
+ 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
+}
diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java
new file mode 100644
index 000000000..3f4a1e9bb
--- /dev/null
+++ b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java
@@ -0,0 +1,106 @@
+package io.sentry.spring.boot;
+
+import com.jakewharton.nopen.annotation.Open;
+import io.sentry.EventProcessor;
+import io.sentry.HubAdapter;
+import io.sentry.IHub;
+import io.sentry.Integration;
+import io.sentry.Sentry;
+import io.sentry.SentryOptions;
+import io.sentry.protocol.SdkVersion;
+import io.sentry.spring.SentryUserProvider;
+import io.sentry.spring.SentryUserProviderEventProcessor;
+import io.sentry.spring.SentryWebConfiguration;
+import io.sentry.transport.ITransport;
+import io.sentry.transport.ITransportGate;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.info.GitProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+@Configuration
+@ConditionalOnProperty(name = "sentry.dsn")
+@Open
+public class SentryAutoConfiguration {
+
+ /** Registers general purpose Sentry related beans. */
+ @Configuration
+ @EnableConfigurationProperties(SentryProperties.class)
+ @Open
+ static class HubConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public @NotNull Sentry.OptionsConfiguration optionsOptionsConfiguration(
+ final @NotNull ObjectProvider beforeSendCallback,
+ final @NotNull ObjectProvider
+ beforeBreadcrumbCallback,
+ final @NotNull List eventProcessors,
+ final @NotNull List integrations,
+ final @NotNull ObjectProvider transportGate,
+ final @NotNull ObjectProvider sentryUserProviders,
+ final @NotNull ObjectProvider transport) {
+ return options -> {
+ beforeSendCallback.ifAvailable(options::setBeforeSend);
+ beforeBreadcrumbCallback.ifAvailable(options::setBeforeBreadcrumb);
+ eventProcessors.forEach(options::addEventProcessor);
+ integrations.forEach(options::addIntegration);
+ sentryUserProviders.forEach(
+ sentryUserProvider ->
+ options.addEventProcessor(
+ new SentryUserProviderEventProcessor(sentryUserProvider)));
+ transportGate.ifAvailable(options::setTransportGate);
+ transport.ifAvailable(options::setTransport);
+ };
+ }
+
+ @Bean
+ public @NotNull IHub sentryHub(
+ final @NotNull Sentry.OptionsConfiguration optionsConfiguration,
+ final @NotNull SentryProperties options,
+ final @NotNull ObjectProvider gitProperties) {
+ optionsConfiguration.configure(options);
+ gitProperties.ifAvailable(
+ git -> {
+ if (options.getRelease() == null && options.isUseGitCommitIdAsRelease()) {
+ options.setRelease(git.getCommitId());
+ }
+ });
+
+ options.setSentryClientName(BuildConfig.SENTRY_SPRING_BOOT_SDK_NAME);
+ options.setSdkVersion(createSdkVersion(options));
+ Sentry.init(options);
+ return HubAdapter.getInstance();
+ }
+
+ /** Registers beans specific to Spring MVC. */
+ @Configuration
+ @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
+ @Import(SentryWebConfiguration.class)
+ @Open
+ static class SentryWebMvcConfiguration {}
+
+ private static @NotNull SdkVersion createSdkVersion(
+ final @NotNull SentryOptions sentryOptions) {
+ SdkVersion sdkVersion = sentryOptions.getSdkVersion();
+
+ if (sdkVersion == null) {
+ sdkVersion = new SdkVersion();
+ }
+
+ sdkVersion.setName(BuildConfig.SENTRY_SPRING_BOOT_SDK_NAME);
+ final String version = BuildConfig.VERSION_NAME;
+ sdkVersion.setVersion(version);
+ sdkVersion.addPackage("maven:sentry-spring-boot-starter", version);
+
+ return sdkVersion;
+ }
+ }
+}
diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryProperties.java b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryProperties.java
new file mode 100644
index 000000000..67bb103a6
--- /dev/null
+++ b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryProperties.java
@@ -0,0 +1,22 @@
+package io.sentry.spring.boot;
+
+import com.jakewharton.nopen.annotation.Open;
+import io.sentry.SentryOptions;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/** Configuration for Sentry integration. */
+@ConfigurationProperties("sentry")
+@Open
+public class SentryProperties extends SentryOptions {
+
+ /** Weather to use Git commit id as a release. */
+ private boolean useGitCommitIdAsRelease = true;
+
+ public boolean isUseGitCommitIdAsRelease() {
+ return useGitCommitIdAsRelease;
+ }
+
+ public void setUseGitCommitIdAsRelease(boolean useGitCommitIdAsRelease) {
+ this.useGitCommitIdAsRelease = useGitCommitIdAsRelease;
+ }
+}
diff --git a/sentry-spring-boot-starter/src/main/resources/META-INF/spring.factories b/sentry-spring-boot-starter/src/main/resources/META-INF/spring.factories
new file mode 100644
index 000000000..2eb623a44
--- /dev/null
+++ b/sentry-spring-boot-starter/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+io.sentry.spring.boot.SentryAutoConfiguration
diff --git a/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt b/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt
new file mode 100644
index 000000000..88dd99fad
--- /dev/null
+++ b/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt
@@ -0,0 +1,290 @@
+package io.sentry.spring.boot
+
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.verify
+import com.nhaarman.mockitokotlin2.whenever
+import io.sentry.Breadcrumb
+import io.sentry.EventProcessor
+import io.sentry.IHub
+import io.sentry.Integration
+import io.sentry.Sentry
+import io.sentry.SentryEvent
+import io.sentry.SentryLevel
+import io.sentry.SentryOptions
+import io.sentry.test.checkEvent
+import io.sentry.transport.ITransport
+import io.sentry.transport.ITransportGate
+import kotlin.test.Test
+import org.assertj.core.api.Assertions.assertThat
+import org.awaitility.kotlin.await
+import org.springframework.boot.autoconfigure.AutoConfigurations
+import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
+import org.springframework.boot.info.GitProperties
+import org.springframework.boot.test.context.runner.ApplicationContextRunner
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+
+class SentryAutoConfigurationTest {
+
+ private val contextRunner = ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(SentryAutoConfiguration::class.java, WebMvcAutoConfiguration::class.java))
+
+ @Test
+ fun `hub is not created when auto-configuration dsn is not set`() {
+ contextRunner
+ .run {
+ assertThat(it).doesNotHaveBean(IHub::class.java)
+ }
+ }
+
+ @Test
+ fun `hub is created when dsn is provided`() {
+ contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
+ .run {
+ assertThat(it).hasSingleBean(IHub::class.java)
+ }
+ }
+
+ @Test
+ fun `OptionsConfiguration is created if custom one is not provided`() {
+ contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
+ .run {
+ assertThat(it).hasSingleBean(Sentry.OptionsConfiguration::class.java)
+ }
+ }
+
+ @Test
+ fun `OptionsConfiguration is not created if custom one is provided`() {
+ contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
+ .withUserConfiguration(CustomOptionsConfigurationConfiguration::class.java)
+ .run {
+ assertThat(it).hasSingleBean(Sentry.OptionsConfiguration::class.java)
+ assertThat(it.getBean(Sentry.OptionsConfiguration::class.java, "customOptionsConfiguration")).isNotNull
+ }
+ }
+
+ @Test
+ fun `properties are applied to SentryOptions`() {
+ contextRunner.withPropertyValues(
+ "sentry.dsn=http://key@localhost/proj",
+ "sentry.read-timeout-millis=10",
+ "sentry.shutdown-timeout=20",
+ "sentry.flush-timeout-millis=30",
+ "sentry.bypass-security=true",
+ "sentry.debug=true",
+ "sentry.diagnostic-level=INFO",
+ "sentry.sentry-client-name=my-client",
+ "sentry.max-breadcrumbs=100",
+ "sentry.release=1.0.3",
+ "sentry.environment=production",
+ "sentry.sample-rate=0.2",
+ "sentry.in-app-excludes[0]=org.springframework",
+ "sentry.in-app-includes[0]=com.myapp",
+ "sentry.dist=my-dist",
+ "sentry.attach-threads=true",
+ "sentry.attach-stacktrace=true",
+ "sentry.server-name=host-001"
+ ).run {
+ val options = it.getBean(SentryOptions::class.java)
+ assertThat(options.readTimeoutMillis).isEqualTo(10)
+ assertThat(options.shutdownTimeout).isEqualTo(20)
+ assertThat(options.flushTimeoutMillis).isEqualTo(30)
+ assertThat(options.isBypassSecurity).isTrue()
+ assertThat(options.isDebug).isTrue()
+ assertThat(options.diagnosticLevel).isEqualTo(SentryLevel.INFO)
+ assertThat(options.maxBreadcrumbs).isEqualTo(100)
+ assertThat(options.release).isEqualTo("1.0.3")
+ assertThat(options.environment).isEqualTo("production")
+ assertThat(options.sampleRate).isEqualTo(0.2)
+ assertThat(options.inAppExcludes).containsOnly("org.springframework")
+ assertThat(options.inAppIncludes).containsOnly("com.myapp")
+ assertThat(options.dist).isEqualTo("my-dist")
+ assertThat(options.isAttachThreads).isEqualTo(true)
+ assertThat(options.isAttachStacktrace).isEqualTo(true)
+ assertThat(options.serverName).isEqualTo("host-001")
+ }
+ }
+
+ @Test
+ fun `sets sentryClientName property on SentryOptions`() {
+ contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
+ .run {
+ assertThat(it.getBean(SentryOptions::class.java).sentryClientName).isEqualTo("sentry.java.spring-boot")
+ }
+ }
+
+ @Test
+ fun `sets SDK version on sent events`() {
+ contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
+ .withUserConfiguration(MockTransportConfiguration::class.java)
+ .run {
+ Sentry.captureMessage("Some message")
+ val transport = it.getBean(ITransport::class.java)
+ await.untilAsserted {
+ verify(transport).send(checkEvent { event ->
+ assertThat(event.sdk.version).isEqualTo(BuildConfig.VERSION_NAME)
+ assertThat(event.sdk.name).isEqualTo(BuildConfig.SENTRY_SPRING_BOOT_SDK_NAME)
+ assertThat(event.sdk.packages).anyMatch { pkg ->
+ pkg.name == "maven:sentry-spring-boot-starter" && pkg.version == BuildConfig.VERSION_NAME
+ }
+ })
+ }
+ }
+ }
+
+ @Test
+ fun `registers beforeSendCallback on SentryOptions`() {
+ contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
+ .withUserConfiguration(CustomBeforeSendCallbackConfiguration::class.java)
+ .run {
+ assertThat(it.getBean(SentryOptions::class.java).beforeSend).isInstanceOf(CustomBeforeSendCallback::class.java)
+ }
+ }
+
+ @Test
+ fun `registers beforeBreadcrumbCallback on SentryOptions`() {
+ contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
+ .withUserConfiguration(CustomBeforeBreadcrumbCallbackConfiguration::class.java)
+ .run {
+ assertThat(it.getBean(SentryOptions::class.java).beforeBreadcrumb).isInstanceOf(CustomBeforeBreadcrumbCallback::class.java)
+ }
+ }
+
+ @Test
+ fun `registers event processor on SentryOptions`() {
+ contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
+ .withUserConfiguration(CustomEventProcessorConfiguration::class.java)
+ .run {
+ assertThat(it.getBean(SentryOptions::class.java).eventProcessors).anyMatch { processor -> processor.javaClass == CustomEventProcessor::class.java }
+ }
+ }
+
+ @Test
+ fun `registers transport gate on SentryOptions`() {
+ contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
+ .withUserConfiguration(CustomTransportGateConfiguration::class.java)
+ .run {
+ assertThat(it.getBean(SentryOptions::class.java).transportGate).isInstanceOf(CustomTransportGate::class.java)
+ }
+ }
+
+ @Test
+ fun `registers custom integration on SentryOptions`() {
+ contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
+ .withUserConfiguration(CustomIntegration::class.java)
+ .run {
+ assertThat(it.getBean(SentryOptions::class.java).integrations).anyMatch { integration -> integration.javaClass == CustomIntegration::class.java }
+ }
+ }
+
+ @Test
+ fun `sets release on SentryEvents if Git integration is configured`() {
+ contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj")
+ .withUserConfiguration(MockTransportConfiguration::class.java, MockGitPropertiesConfiguration::class.java)
+ .run {
+ Sentry.captureMessage("Some message")
+ val transport = it.getBean(ITransport::class.java)
+ await.untilAsserted {
+ verify(transport).send(checkEvent { event ->
+ assertThat(event.release).isEqualTo("git-commit-id")
+ })
+ }
+ }
+ }
+
+ @Test
+ fun `sets custom release on SentryEvents if release property is set and Git integration is configured`() {
+ contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.release=my-release")
+ .withUserConfiguration(MockTransportConfiguration::class.java, MockGitPropertiesConfiguration::class.java)
+ .run {
+ Sentry.captureMessage("Some message")
+ val transport = it.getBean(ITransport::class.java)
+ await.untilAsserted {
+ verify(transport).send(checkEvent { event ->
+ assertThat(event.release).isEqualTo("my-release")
+ })
+ }
+ }
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ open class CustomOptionsConfigurationConfiguration {
+
+ @Bean
+ open fun customOptionsConfiguration() = Sentry.OptionsConfiguration {
+ }
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ open class MockTransportConfiguration {
+
+ @Bean
+ open fun sentryTransport() = mock()
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ open class CustomBeforeSendCallbackConfiguration {
+
+ @Bean
+ open fun beforeSendCallback() = CustomBeforeSendCallback()
+ }
+
+ class CustomBeforeSendCallback : SentryOptions.BeforeSendCallback {
+ override fun execute(event: SentryEvent, hint: Any?): SentryEvent? = null
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ open class CustomBeforeBreadcrumbCallbackConfiguration {
+
+ @Bean
+ open fun beforeBreadcrumbCallback() = CustomBeforeBreadcrumbCallback()
+ }
+
+ class CustomBeforeBreadcrumbCallback : SentryOptions.BeforeBreadcrumbCallback {
+ override fun execute(breadcrumb: Breadcrumb, hint: Any?): Breadcrumb? = null
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ open class CustomEventProcessorConfiguration {
+
+ @Bean
+ open fun customEventProcessor() = CustomEventProcessor()
+ }
+
+ class CustomEventProcessor : EventProcessor {
+ override fun process(event: SentryEvent?, hint: Any?) = null
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ open class CustomIntegrationConfiguration {
+
+ @Bean
+ open fun customIntegration() = CustomIntegration()
+ }
+
+ class CustomIntegration : Integration {
+ override fun register(hub: IHub?, options: SentryOptions?) {}
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ open class CustomTransportGateConfiguration {
+
+ @Bean
+ open fun customTransportGate() = CustomTransportGate()
+ }
+
+ class CustomTransportGate : ITransportGate {
+ override fun isConnected() = true
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ open class MockGitPropertiesConfiguration {
+
+ @Bean
+ open fun gitProperties(): GitProperties {
+ val git = mock()
+ whenever(git.commitId).thenReturn("git-commit-id")
+ return git
+ }
+ }
+}
diff --git a/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt b/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt
new file mode 100644
index 000000000..808c7ce1f
--- /dev/null
+++ b/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt
@@ -0,0 +1,140 @@
+package io.sentry.spring.boot
+
+import com.nhaarman.mockitokotlin2.verify
+import io.sentry.Sentry
+import io.sentry.test.checkEvent
+import io.sentry.transport.ITransport
+import java.lang.RuntimeException
+import org.assertj.core.api.Assertions.assertThat
+import org.awaitility.kotlin.await
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.boot.test.mock.mockito.MockBean
+import org.springframework.boot.test.web.client.TestRestTemplate
+import org.springframework.boot.web.server.LocalServerPort
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.http.HttpEntity
+import org.springframework.http.HttpHeaders
+import org.springframework.http.HttpMethod
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.core.userdetails.UserDetails
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.crypto.factory.PasswordEncoderFactories
+import org.springframework.security.crypto.password.PasswordEncoder
+import org.springframework.security.provisioning.InMemoryUserDetailsManager
+import org.springframework.test.context.junit4.SpringRunner
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RestController
+
+@RunWith(SpringRunner::class)
+@SpringBootTest(
+ classes = [App::class],
+ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ properties = ["sentry.dsn=http://key@localhost/proj", "sentry.send-default-pii=true"]
+)
+class SentrySpringIntegrationTest {
+
+ @MockBean
+ lateinit var transport: ITransport
+
+ @LocalServerPort
+ lateinit var port: Integer
+
+ @Test
+ fun `attaches request and user information to SentryEvents`() {
+ val restTemplate = TestRestTemplate().withBasicAuth("user", "password")
+ val headers = HttpHeaders()
+ headers["X-FORWARDED-FOR"] = listOf("169.128.0.1")
+ val entity = HttpEntity(headers)
+
+ restTemplate.exchange("http://localhost:$port/hello", HttpMethod.GET, entity, Void::class.java)
+
+ await.untilAsserted {
+ verify(transport).send(checkEvent { event ->
+ assertThat(event.request).isNotNull()
+ assertThat(event.request.url).isEqualTo("http://localhost:$port/hello")
+ assertThat(event.user).isNotNull()
+ assertThat(event.user.username).isEqualTo("user")
+ assertThat(event.user.ipAddress).isEqualTo("169.128.0.1")
+ })
+ }
+ }
+
+ @Test
+ fun `attaches first ip address if multiple addresses exist in a header`() {
+ val restTemplate = TestRestTemplate().withBasicAuth("user", "password")
+ val headers = HttpHeaders()
+ headers["X-FORWARDED-FOR"] = listOf("169.128.0.1, 192.168.0.1")
+ val entity = HttpEntity(headers)
+
+ restTemplate.exchange("http://localhost:$port/hello", HttpMethod.GET, entity, Void::class.java)
+
+ await.untilAsserted {
+ verify(transport).send(checkEvent { event ->
+ assertThat(event.user.ipAddress).isEqualTo("169.128.0.1")
+ })
+ }
+ }
+
+ @Test
+ fun `sends events for unhandled exceptions`() {
+ val restTemplate = TestRestTemplate().withBasicAuth("user", "password")
+
+ restTemplate.getForEntity("http://localhost:$port/throws", String::class.java)
+
+ await.untilAsserted {
+ verify(transport).send(checkEvent { event ->
+ assertThat(event.exceptions).isNotEmpty
+ val ex = event.exceptions.first()
+ assertThat(ex.value).isEqualTo("something went wrong")
+ assertThat(ex.mechanism.isHandled).isFalse()
+ })
+ }
+ }
+}
+
+@SpringBootApplication
+open class App
+
+@RestController
+class HelloController {
+
+ @GetMapping("/hello")
+ fun hello() {
+ Sentry.captureMessage("hello")
+ }
+
+ @GetMapping("/throws")
+ fun throws() {
+ throw RuntimeException("something went wrong")
+ }
+}
+
+@Configuration
+open class SecurityConfiguration : WebSecurityConfigurerAdapter() {
+
+ override fun configure(http: HttpSecurity) {
+ http.csrf().disable()
+ .authorizeRequests().anyRequest().authenticated()
+ .and()
+ .httpBasic()
+ }
+
+ @Bean
+ override fun userDetailsService(): UserDetailsService {
+ val encoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
+ val user: UserDetails = User
+ .builder()
+ .passwordEncoder { rawPassword -> encoder.encode(rawPassword) }
+ .username("user")
+ .password("password")
+ .roles("USER")
+ .build()
+ return InMemoryUserDetailsManager(user)
+ }
+}
diff --git a/sentry-spring/build.gradle.kts b/sentry-spring/build.gradle.kts
new file mode 100644
index 000000000..fc31b4fd0
--- /dev/null
+++ b/sentry-spring/build.gradle.kts
@@ -0,0 +1,119 @@
+import com.novoda.gradle.release.PublishExtension
+import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import org.springframework.boot.gradle.plugin.SpringBootPlugin
+
+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
+ id(Config.BuildPlugins.springBoot) version Config.springBootVersion apply false
+}
+
+apply(plugin = Config.BuildPlugins.springDependencyManagement)
+
+the().apply {
+ imports {
+ mavenBom(SpringBootPlugin.BOM_COORDINATES)
+ }
+}
+
+configure {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+tasks.withType().configureEach {
+ kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
+ kotlinOptions.languageVersion = Config.springKotlinCompatibleLanguageVersion
+}
+
+dependencies {
+ api(project(":sentry"))
+ implementation(Config.Libs.springWeb)
+ implementation(Config.Libs.servletApi)
+
+ compileOnly(Config.CompileOnly.nopen)
+ errorprone(Config.CompileOnly.nopenChecker)
+ errorprone(Config.CompileOnly.errorprone)
+ errorproneJavac(Config.CompileOnly.errorProneJavac8)
+ compileOnly(Config.CompileOnly.jetbrainsAnnotations)
+
+ // tests
+ testImplementation(project(":sentry-test-support"))
+ testImplementation(kotlin(Config.kotlinStdLib))
+ testImplementation(Config.TestLibs.kotlinTestJunit)
+ testImplementation(Config.TestLibs.mockitoKotlin)
+ testImplementation(Config.Libs.springBootStarterTest)
+ testImplementation(Config.Libs.springBootStarterWeb)
+ testImplementation(Config.Libs.springBootStarterSecurity)
+ testImplementation(Config.TestLibs.awaitility)
+}
+
+configure {
+ test {
+ java.srcDir("src/test/java")
+ }
+}
+
+jacoco {
+ toolVersion = Config.QualityPlugins.Jacoco.version
+}
+
+tasks.jacocoTestReport {
+ reports {
+ xml.isEnabled = true
+ html.isEnabled = false
+ }
+}
+
+tasks {
+ jacocoTestCoverageVerification {
+ violationRules {
+ rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } }
+ }
+ }
+ check {
+ dependsOn(jacocoTestCoverageVerification)
+ dependsOn(jacocoTestReport)
+ }
+}
+
+buildConfig {
+ useJavaOutput()
+ packageName("io.sentry.spring")
+ buildConfigField("String", "SENTRY_SPRING_SDK_NAME", "\"${Config.Sentry.SENTRY_SPRING_SDK_NAME}\"")
+ buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
+}
+
+val generateBuildConfig by tasks
+tasks.withType().configureEach {
+ dependsOn(generateBuildConfig)
+}
+
+// TODO: move these blocks to parent gradle file, DRY
+configure {
+ 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
+}
diff --git a/sentry-spring/src/main/java/io/sentry/spring/EnableSentry.java b/sentry-spring/src/main/java/io/sentry/spring/EnableSentry.java
new file mode 100644
index 000000000..17b24d66c
--- /dev/null
+++ b/sentry-spring/src/main/java/io/sentry/spring/EnableSentry.java
@@ -0,0 +1,32 @@
+package io.sentry.spring;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.springframework.context.annotation.Import;
+
+/**
+ * Enables Sentry error handling capabilities.
+ *
+ *
+ * - creates bean of type {@link io.sentry.SentryOptions}
+ *
- registers {@link io.sentry.IHub} for sending Sentry events
+ *
- registers {@link SentryRequestFilter} for attaching request information to Sentry events
+ *
- registers {@link SentryExceptionResolver} to send Sentry event for any uncaught exception
+ * in Spring MVC flow.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Import({SentryHubRegistrar.class, SentryInitBeanPostProcessor.class, SentryWebConfiguration.class})
+@Target(ElementType.TYPE)
+public @interface EnableSentry {
+ /**
+ * The DSN tells the SDK where to send the events to. If this value is not provided, the SDK will
+ * just not send any events.
+ */
+ String dsn() default "";
+
+ /** Whether to send personal identifiable information along with events. */
+ boolean sendDefaultPii() default false;
+}
diff --git a/sentry-spring/src/main/java/io/sentry/spring/HttpServletRequestSentryUserProvider.java b/sentry-spring/src/main/java/io/sentry/spring/HttpServletRequestSentryUserProvider.java
new file mode 100644
index 000000000..c24d2c2ff
--- /dev/null
+++ b/sentry-spring/src/main/java/io/sentry/spring/HttpServletRequestSentryUserProvider.java
@@ -0,0 +1,55 @@
+package io.sentry.spring;
+
+import io.sentry.SentryOptions;
+import io.sentry.protocol.User;
+import io.sentry.util.Objects;
+import javax.servlet.http.HttpServletRequest;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+/**
+ * Resolves user information from {@link HttpServletRequest} obtained via {@link
+ * RequestContextHolder}.
+ */
+public final class HttpServletRequestSentryUserProvider implements SentryUserProvider {
+ private final @NotNull SentryOptions options;
+
+ public HttpServletRequestSentryUserProvider(final @NotNull SentryOptions options) {
+ this.options = Objects.requireNonNull(options, "options are required");
+ }
+
+ @Override
+ public @Nullable User provideUser() {
+ if (options.isSendDefaultPii()) {
+ final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+ if (requestAttributes instanceof ServletRequestAttributes) {
+ final ServletRequestAttributes servletRequestAttributes =
+ (ServletRequestAttributes) requestAttributes;
+ final HttpServletRequest request = servletRequestAttributes.getRequest();
+
+ final User user = new User();
+ user.setIpAddress(toIpAddress(request));
+ if (request.getUserPrincipal() != null) {
+ user.setUsername(request.getUserPrincipal().getName());
+ }
+ return user;
+ }
+ }
+ return null;
+ }
+
+ // it is advised to not use `String#split` method but since we do not have 3rd party libraries
+ // this is our only option.
+ @SuppressWarnings("StringSplitter")
+ private static @NotNull String toIpAddress(final @NotNull HttpServletRequest request) {
+ final String ipAddress = request.getHeader("X-FORWARDED-FOR");
+ if (ipAddress != null) {
+ return ipAddress.contains(",") ? ipAddress.split(",")[0].trim() : ipAddress;
+ } else {
+ return request.getRemoteAddr();
+ }
+ }
+}
diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryExceptionResolver.java b/sentry-spring/src/main/java/io/sentry/spring/SentryExceptionResolver.java
new file mode 100644
index 000000000..30c76a680
--- /dev/null
+++ b/sentry-spring/src/main/java/io/sentry/spring/SentryExceptionResolver.java
@@ -0,0 +1,55 @@
+package io.sentry.spring;
+
+import com.jakewharton.nopen.annotation.Open;
+import io.sentry.IHub;
+import io.sentry.SentryEvent;
+import io.sentry.SentryLevel;
+import io.sentry.exception.ExceptionMechanismException;
+import io.sentry.protocol.Mechanism;
+import io.sentry.util.Objects;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.springframework.core.Ordered;
+import org.springframework.web.servlet.HandlerExceptionResolver;
+import org.springframework.web.servlet.ModelAndView;
+
+/**
+ * {@link HandlerExceptionResolver} implementation that will record any exception that a Spring
+ * {@link org.springframework.web.servlet.mvc.Controller} throws to Sentry. It then returns null,
+ * which will let the other (default or custom) exception resolvers handle the actual error.
+ */
+@Open
+public class SentryExceptionResolver implements HandlerExceptionResolver, Ordered {
+ private final @NotNull IHub hub;
+
+ public SentryExceptionResolver(final @NotNull IHub hub) {
+ this.hub = Objects.requireNonNull(hub, "hub is required");
+ }
+
+ @Override
+ public @Nullable ModelAndView resolveException(
+ final @NotNull HttpServletRequest request,
+ final @NotNull HttpServletResponse response,
+ final @Nullable Object handler,
+ final @NotNull Exception ex) {
+
+ final Mechanism mechanism = new Mechanism();
+ mechanism.setHandled(false);
+ final Throwable throwable =
+ new ExceptionMechanismException(mechanism, ex, Thread.currentThread());
+ final SentryEvent event = new SentryEvent(throwable);
+ event.setLevel(SentryLevel.FATAL);
+ hub.captureEvent(event);
+
+ // null = run other HandlerExceptionResolvers to actually handle the exception
+ return null;
+ }
+
+ @Override
+ public int getOrder() {
+ // ensure this resolver runs first so that all exceptions are reported
+ return Integer.MIN_VALUE;
+ }
+}
diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryHubRegistrar.java b/sentry-spring/src/main/java/io/sentry/spring/SentryHubRegistrar.java
new file mode 100644
index 000000000..0fc33653d
--- /dev/null
+++ b/sentry-spring/src/main/java/io/sentry/spring/SentryHubRegistrar.java
@@ -0,0 +1,73 @@
+package io.sentry.spring;
+
+import com.jakewharton.nopen.annotation.Open;
+import io.sentry.HubAdapter;
+import io.sentry.SentryOptions;
+import io.sentry.protocol.SdkVersion;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.type.AnnotationMetadata;
+
+/** Registers beans required to use Sentry core features. */
+@Configuration
+@Open
+public class SentryHubRegistrar implements ImportBeanDefinitionRegistrar {
+
+ @Override
+ public void registerBeanDefinitions(
+ AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
+ final AnnotationAttributes annotationAttributes =
+ AnnotationAttributes.fromMap(
+ importingClassMetadata.getAnnotationAttributes(EnableSentry.class.getName()));
+ if (annotationAttributes != null && annotationAttributes.containsKey("dsn")) {
+ registerSentryOptions(registry, annotationAttributes);
+ registerSentryHubBean(registry);
+ }
+ }
+
+ private void registerSentryOptions(
+ BeanDefinitionRegistry registry, AnnotationAttributes annotationAttributes) {
+ final BeanDefinitionBuilder builder =
+ BeanDefinitionBuilder.genericBeanDefinition(SentryOptions.class);
+
+ if (registry.containsBeanDefinition("mockTransport")) {
+ builder.addPropertyReference("transport", "mockTransport");
+ }
+ builder.addPropertyValue("dsn", annotationAttributes.getString("dsn"));
+ builder.addPropertyValue("sentryClientName", BuildConfig.SENTRY_SPRING_SDK_NAME);
+ builder.addPropertyValue("sdkVersion", createSdkVersion());
+ if (annotationAttributes.containsKey("sendDefaultPii")) {
+ builder.addPropertyValue("sendDefaultPii", annotationAttributes.getBoolean("sendDefaultPii"));
+ }
+
+ registry.registerBeanDefinition("sentryOptions", builder.getBeanDefinition());
+ }
+
+ private void registerSentryHubBean(BeanDefinitionRegistry registry) {
+ final BeanDefinitionBuilder builder =
+ BeanDefinitionBuilder.genericBeanDefinition(HubAdapter.class);
+ builder.setInitMethodName("getInstance");
+
+ registry.registerBeanDefinition("sentryHub", builder.getBeanDefinition());
+ }
+
+ private static @NotNull SdkVersion createSdkVersion() {
+ final SentryOptions defaultOptions = new SentryOptions();
+ SdkVersion sdkVersion = defaultOptions.getSdkVersion();
+
+ if (sdkVersion == null) {
+ sdkVersion = new SdkVersion();
+ }
+
+ sdkVersion.setName(BuildConfig.SENTRY_SPRING_SDK_NAME);
+ final String version = BuildConfig.VERSION_NAME;
+ sdkVersion.setVersion(version);
+ sdkVersion.addPackage("maven:sentry-spring", version);
+
+ return sdkVersion;
+ }
+}
diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryInitBeanPostProcessor.java b/sentry-spring/src/main/java/io/sentry/spring/SentryInitBeanPostProcessor.java
new file mode 100644
index 000000000..96c57b7f6
--- /dev/null
+++ b/sentry-spring/src/main/java/io/sentry/spring/SentryInitBeanPostProcessor.java
@@ -0,0 +1,42 @@
+package io.sentry.spring;
+
+import com.jakewharton.nopen.annotation.Open;
+import io.sentry.Sentry;
+import io.sentry.SentryOptions;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+/** Initializes Sentry after all beans are registered. */
+@Open
+public class SentryInitBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
+ private @Nullable ApplicationContext applicationContext;
+
+ @Override
+ public Object postProcessAfterInitialization(
+ final @NotNull Object bean, @NotNull final String beanName) throws BeansException {
+ if (bean instanceof SentryOptions) {
+ final SentryOptions options = (SentryOptions) bean;
+
+ if (applicationContext != null) {
+ applicationContext
+ .getBeanProvider(SentryUserProvider.class)
+ .forEach(
+ sentryUserProvider ->
+ options.addEventProcessor(
+ new SentryUserProviderEventProcessor(sentryUserProvider)));
+ }
+ Sentry.init(options);
+ }
+ return bean;
+ }
+
+ @Override
+ public void setApplicationContext(final @NotNull ApplicationContext applicationContext)
+ throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+}
diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java
new file mode 100644
index 000000000..f72d3b3d5
--- /dev/null
+++ b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestFilter.java
@@ -0,0 +1,48 @@
+package io.sentry.spring;
+
+import com.jakewharton.nopen.annotation.Open;
+import io.sentry.Breadcrumb;
+import io.sentry.IHub;
+import io.sentry.SentryOptions;
+import io.sentry.util.Objects;
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.core.Ordered;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+/** Pushes new {@link io.sentry.Scope} on each incoming HTTP request. */
+@Open
+public class SentryRequestFilter extends OncePerRequestFilter implements Ordered {
+ private final @NotNull IHub hub;
+ private final @NotNull SentryOptions options;
+
+ public SentryRequestFilter(final @NotNull IHub hub, final @NotNull SentryOptions options) {
+ this.hub = Objects.requireNonNull(hub, "hub is required");
+ this.options = Objects.requireNonNull(options, "options are required");
+ }
+
+ @Override
+ protected void doFilterInternal(
+ final @NotNull HttpServletRequest request,
+ final @NotNull HttpServletResponse response,
+ final @NotNull FilterChain filterChain)
+ throws ServletException, IOException {
+ hub.pushScope();
+ hub.addBreadcrumb(Breadcrumb.http(request.getRequestURI(), request.getMethod()));
+
+ hub.configureScope(
+ scope -> {
+ scope.addEventProcessor(new SentryRequestHttpServletRequestProcessor(request, options));
+ });
+ filterChain.doFilter(request, response);
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.HIGHEST_PRECEDENCE;
+ }
+}
diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestHttpServletRequestProcessor.java b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestHttpServletRequestProcessor.java
new file mode 100644
index 000000000..d3f54ae64
--- /dev/null
+++ b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestHttpServletRequestProcessor.java
@@ -0,0 +1,71 @@
+package io.sentry.spring;
+
+import com.jakewharton.nopen.annotation.Open;
+import io.sentry.EventProcessor;
+import io.sentry.SentryEvent;
+import io.sentry.SentryOptions;
+import io.sentry.protocol.Request;
+import io.sentry.util.Objects;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Attaches information about HTTP request to {@link SentryEvent}. */
+@Open
+public class SentryRequestHttpServletRequestProcessor implements EventProcessor {
+ private static final List SENSITIVE_HEADERS =
+ Arrays.asList("X-FORWARDED-FOR", "AUTHORIZATION", "COOKIES");
+
+ private final @NotNull HttpServletRequest request;
+ private final @NotNull SentryOptions options;
+
+ public SentryRequestHttpServletRequestProcessor(
+ final @NotNull HttpServletRequest request, final @NotNull SentryOptions options) {
+ this.request = Objects.requireNonNull(request, "request is required");
+ this.options = Objects.requireNonNull(options, "options are required");
+ }
+
+ @Override
+ public @NotNull SentryEvent process(
+ final @NotNull SentryEvent event, final @Nullable Object hint) {
+ event.setRequest(resolveSentryRequest(request));
+ return event;
+ }
+
+ // httpRequest.getRequestURL() returns StringBuffer which is considered an obsolete class.
+ @SuppressWarnings("JdkObsolete")
+ private @NotNull Request resolveSentryRequest(final @NotNull HttpServletRequest httpRequest) {
+ final Request sentryRequest = new Request();
+ sentryRequest.setMethod(httpRequest.getMethod());
+ sentryRequest.setQueryString(httpRequest.getQueryString());
+ sentryRequest.setUrl(httpRequest.getRequestURL().toString());
+ sentryRequest.setHeaders(resolveHeadersMap(httpRequest));
+
+ if (options.isSendDefaultPii()) {
+ sentryRequest.setCookies(toString(httpRequest.getHeaders("Cookie")));
+ }
+ return sentryRequest;
+ }
+
+ private @NotNull Map resolveHeadersMap(
+ final @NotNull HttpServletRequest request) {
+ final Map headersMap = new HashMap<>();
+ for (String headerName : Collections.list(request.getHeaderNames())) {
+ // do not copy personal information identifiable headers
+ if (options.isSendDefaultPii() || !SENSITIVE_HEADERS.contains(headerName.toUpperCase())) {
+ headersMap.put(headerName, toString(request.getHeaders(headerName)));
+ }
+ }
+ return headersMap;
+ }
+
+ private static @Nullable String toString(final @Nullable Enumeration enumeration) {
+ return enumeration != null ? String.join(",", Collections.list(enumeration)) : null;
+ }
+}
diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryUserProvider.java b/sentry-spring/src/main/java/io/sentry/spring/SentryUserProvider.java
new file mode 100644
index 000000000..6ee1f899a
--- /dev/null
+++ b/sentry-spring/src/main/java/io/sentry/spring/SentryUserProvider.java
@@ -0,0 +1,17 @@
+package io.sentry.spring;
+
+import io.sentry.protocol.User;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Provides user information that's set on {@link io.sentry.SentryEvent} using {@link
+ * SentryUserProviderEventProcessor}.
+ *
+ * Out of the box Spring integration configures single {@link SentryUserProvider} - {@link
+ * HttpServletRequestSentryUserProvider}.
+ */
+@FunctionalInterface
+public interface SentryUserProvider {
+ @Nullable
+ User provideUser();
+}
diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryUserProviderEventProcessor.java b/sentry-spring/src/main/java/io/sentry/spring/SentryUserProviderEventProcessor.java
new file mode 100644
index 000000000..2e5370926
--- /dev/null
+++ b/sentry-spring/src/main/java/io/sentry/spring/SentryUserProviderEventProcessor.java
@@ -0,0 +1,48 @@
+package io.sentry.spring;
+
+import io.sentry.EventProcessor;
+import io.sentry.SentryEvent;
+import io.sentry.protocol.User;
+import io.sentry.util.Objects;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public final class SentryUserProviderEventProcessor implements EventProcessor {
+ private final @NotNull SentryUserProvider sentryUserProvider;
+
+ public SentryUserProviderEventProcessor(final @NotNull SentryUserProvider sentryUserProvider) {
+ this.sentryUserProvider =
+ Objects.requireNonNull(sentryUserProvider, "sentryUserProvider is required");
+ }
+
+ @Override
+ public SentryEvent process(final @NotNull SentryEvent event, final @Nullable Object hint) {
+ final User user = sentryUserProvider.provideUser();
+ if (user != null) {
+ final User existingUser = Optional.ofNullable(event.getUser()).orElseGet(User::new);
+
+ Optional.ofNullable(user.getEmail()).ifPresent(existingUser::setEmail);
+ Optional.ofNullable(user.getId()).ifPresent(existingUser::setId);
+ Optional.ofNullable(user.getIpAddress()).ifPresent(existingUser::setIpAddress);
+ Optional.ofNullable(user.getUsername()).ifPresent(existingUser::setUsername);
+ if (user.getOthers() != null && !user.getOthers().isEmpty()) {
+ if (existingUser.getOthers() == null) {
+ existingUser.setOthers(new ConcurrentHashMap<>());
+ }
+ for (Map.Entry entry : user.getOthers().entrySet()) {
+ existingUser.getOthers().put(entry.getKey(), entry.getValue());
+ }
+ }
+ event.setUser(existingUser);
+ }
+ return event;
+ }
+
+ @NotNull
+ SentryUserProvider getSentryUserProvider() {
+ return sentryUserProvider;
+ }
+}
diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryWebConfiguration.java b/sentry-spring/src/main/java/io/sentry/spring/SentryWebConfiguration.java
new file mode 100644
index 000000000..86aaf6657
--- /dev/null
+++ b/sentry-spring/src/main/java/io/sentry/spring/SentryWebConfiguration.java
@@ -0,0 +1,33 @@
+package io.sentry.spring;
+
+import com.jakewharton.nopen.annotation.Open;
+import io.sentry.IHub;
+import io.sentry.SentryOptions;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+
+/** Registers Spring Web specific Sentry beans. */
+@Configuration
+@Open
+public class SentryWebConfiguration {
+
+ @Bean
+ @Lazy
+ public @NotNull HttpServletRequestSentryUserProvider httpServletRequestSentryUserProvider(
+ final @NotNull SentryOptions sentryOptions) {
+ return new HttpServletRequestSentryUserProvider(sentryOptions);
+ }
+
+ @Bean
+ public @NotNull SentryRequestFilter sentryRequestFilter(
+ final @NotNull IHub sentryHub, final @NotNull SentryOptions sentryOptions) {
+ return new SentryRequestFilter(sentryHub, sentryOptions);
+ }
+
+ @Bean
+ public @NotNull SentryExceptionResolver sentryExceptionResolver(final @NotNull IHub sentryHub) {
+ return new SentryExceptionResolver(sentryHub);
+ }
+}
diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/EnableSentryTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/EnableSentryTest.kt
new file mode 100644
index 000000000..df7e8a5f0
--- /dev/null
+++ b/sentry-spring/src/test/kotlin/io/sentry/spring/EnableSentryTest.kt
@@ -0,0 +1,78 @@
+package io.sentry.spring
+
+import io.sentry.IHub
+import io.sentry.SentryOptions
+import kotlin.test.Test
+import org.assertj.core.api.Assertions.assertThat
+import org.springframework.boot.context.annotation.UserConfigurations
+import org.springframework.boot.test.context.runner.ApplicationContextRunner
+
+class EnableSentryTest {
+ private val contextRunner = ApplicationContextRunner()
+ .withConfiguration(UserConfigurations.of(AppConfig::class.java))
+
+ @Test
+ fun `sets properties from environment on SentryOptions`() {
+ ApplicationContextRunner()
+ .withConfiguration(UserConfigurations.of(AppConfigWithDefaultSendPii::class.java))
+ .run {
+ assertThat(it).hasSingleBean(SentryOptions::class.java)
+ val options = it.getBean(SentryOptions::class.java)
+ assertThat(options.dsn).isEqualTo("http://key@localhost/proj")
+ assertThat(options.isSendDefaultPii).isTrue()
+ }
+
+ ApplicationContextRunner()
+ .withConfiguration(UserConfigurations.of(AppConfigWithEmptyDsn::class.java))
+ .run {
+ assertThat(it).hasSingleBean(SentryOptions::class.java)
+ val options = it.getBean(SentryOptions::class.java)
+ assertThat(options.dsn).isEmpty()
+ assertThat(options.isSendDefaultPii).isFalse()
+ }
+ }
+
+ @Test
+ fun `sets client name and SDK version`() {
+ contextRunner.run {
+ assertThat(it).hasSingleBean(SentryOptions::class.java)
+ val options = it.getBean(SentryOptions::class.java)
+ assertThat(options.sentryClientName).isEqualTo("sentry.java.spring")
+ assertThat(options.sdkVersion).isNotNull
+ assertThat(options.sdkVersion!!.name).isEqualTo("sentry.java.spring")
+ assertThat(options.sdkVersion!!.version).isEqualTo(BuildConfig.VERSION_NAME)
+ assertThat(options.sdkVersion!!.packages).isNotNull
+ assertThat(options.sdkVersion!!.packages!!.map { pkg -> pkg.name }).contains("maven:sentry-spring")
+ }
+ }
+
+ @Test
+ fun `creates Sentry Hub`() {
+ contextRunner.run {
+ assertThat(it).hasSingleBean(IHub::class.java)
+ }
+ }
+
+ @Test
+ fun `creates SentryRequestFilter`() {
+ contextRunner.run {
+ assertThat(it).hasSingleBean(SentryRequestFilter::class.java)
+ }
+ }
+
+ @Test
+ fun `creates SentryExceptionResolver`() {
+ contextRunner.run {
+ assertThat(it).hasSingleBean(SentryExceptionResolver::class.java)
+ }
+ }
+
+ @EnableSentry(dsn = "http://key@localhost/proj")
+ class AppConfig
+
+ @EnableSentry(dsn = "")
+ class AppConfigWithEmptyDsn
+
+ @EnableSentry(dsn = "http://key@localhost/proj", sendDefaultPii = true)
+ class AppConfigWithDefaultSendPii
+}
diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/HttpServletRequestSentryUserProviderTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/HttpServletRequestSentryUserProviderTest.kt
new file mode 100644
index 000000000..20b2617cd
--- /dev/null
+++ b/sentry-spring/src/test/kotlin/io/sentry/spring/HttpServletRequestSentryUserProviderTest.kt
@@ -0,0 +1,64 @@
+package io.sentry.spring
+
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.whenever
+import io.sentry.SentryOptions
+import java.security.Principal
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import org.springframework.mock.web.MockHttpServletRequest
+import org.springframework.web.context.request.RequestContextHolder
+import org.springframework.web.context.request.ServletRequestAttributes
+
+class HttpServletRequestSentryUserProviderTest {
+
+ @Test
+ fun `attaches user's IP address to Sentry Event`() {
+ val request = MockHttpServletRequest()
+ request.addHeader("X-FORWARDED-FOR", "192.168.0.1,192.168.0.2")
+ RequestContextHolder.setRequestAttributes(ServletRequestAttributes(request))
+
+ val options = SentryOptions()
+ options.isSendDefaultPii = true
+ val userProvider = HttpServletRequestSentryUserProvider(options)
+ val result = userProvider.provideUser()
+
+ assertNotNull(result)
+ assertEquals("192.168.0.1", result.ipAddress)
+ }
+
+ @Test
+ fun `attaches username to Sentry Event`() {
+ val principal = mock()
+ whenever(principal.name).thenReturn("janesmith")
+ val request = MockHttpServletRequest()
+ request.userPrincipal = principal
+ RequestContextHolder.setRequestAttributes(ServletRequestAttributes(request))
+
+ val options = SentryOptions()
+ options.isSendDefaultPii = true
+ val userProvider = HttpServletRequestSentryUserProvider(options)
+ val result = userProvider.provideUser()
+
+ assertNotNull(result)
+ assertEquals("janesmith", result.username)
+ }
+
+ @Test
+ fun `when sendDefaultPii is set to false, does not attach user data Sentry Event`() {
+ val principal = mock()
+ whenever(principal.name).thenReturn("janesmith")
+ val request = MockHttpServletRequest()
+ request.userPrincipal = principal
+ RequestContextHolder.setRequestAttributes(ServletRequestAttributes(request))
+
+ val options = SentryOptions()
+ options.isSendDefaultPii = false
+ val userProvider = HttpServletRequestSentryUserProvider(options)
+ val result = userProvider.provideUser()
+
+ assertNull(result)
+ }
+}
diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/SentryRequestHttpServletRequestProcessorTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/SentryRequestHttpServletRequestProcessorTest.kt
new file mode 100644
index 000000000..ed4bcbf8f
--- /dev/null
+++ b/sentry-spring/src/test/kotlin/io/sentry/spring/SentryRequestHttpServletRequestProcessorTest.kt
@@ -0,0 +1,111 @@
+package io.sentry.spring
+
+import io.sentry.SentryEvent
+import io.sentry.SentryOptions
+import java.net.URI
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import org.springframework.http.MediaType
+import org.springframework.mock.web.MockServletContext
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
+
+class SentryRequestHttpServletRequestProcessorTest {
+
+ @Test
+ fun `attaches basic information from HTTP request to SentryEvent`() {
+ val request = MockMvcRequestBuilders
+ .get(URI.create("http://example.com?param1=xyz"))
+ .header("some-header", "some-header value")
+ .accept(MediaType.APPLICATION_JSON)
+ .buildRequest(MockServletContext())
+ val eventProcessor = SentryRequestHttpServletRequestProcessor(request, SentryOptions())
+ val event = SentryEvent()
+
+ eventProcessor.process(event, null)
+
+ assertEquals("GET", event.request.method)
+ assertEquals(mapOf(
+ "some-header" to "some-header value",
+ "Accept" to "application/json"
+ ), event.request.headers)
+ assertEquals("http://example.com", event.request.url)
+ assertEquals("param1=xyz", event.request.queryString)
+ }
+
+ @Test
+ fun `attaches header with multiple values`() {
+ val request = MockMvcRequestBuilders
+ .get(URI.create("http://example.com?param1=xyz"))
+ .header("another-header", "another value")
+ .header("another-header", "another value2")
+ .buildRequest(MockServletContext())
+ val eventProcessor = SentryRequestHttpServletRequestProcessor(request, SentryOptions())
+ val event = SentryEvent()
+
+ eventProcessor.process(event, null)
+
+ assertEquals(mapOf(
+ "another-header" to "another value,another value2"
+ ), event.request.headers)
+ }
+
+ @Test
+ fun `when sendDefaultPii is set to true, attaches cookies information`() {
+ val request = MockMvcRequestBuilders
+ .get(URI.create("http://example.com?param1=xyz"))
+ .header("Cookie", "name=value")
+ .header("Cookie", "name2=value2")
+ .buildRequest(MockServletContext())
+ val sentryOptions = SentryOptions()
+ sentryOptions.isSendDefaultPii = true
+ val eventProcessor = SentryRequestHttpServletRequestProcessor(request, sentryOptions)
+ val event = SentryEvent()
+
+ eventProcessor.process(event, null)
+
+ assertEquals("name=value,name2=value2", event.request.cookies)
+ }
+
+ @Test
+ fun `when sendDefaultPii is set to false, does not attach cookies`() {
+ val request = MockMvcRequestBuilders
+ .get(URI.create("http://example.com?param1=xyz"))
+ .header("Cookie", "name=value")
+ .buildRequest(MockServletContext())
+ val sentryOptions = SentryOptions()
+ sentryOptions.isSendDefaultPii = false
+ val eventProcessor = SentryRequestHttpServletRequestProcessor(request, sentryOptions)
+ val event = SentryEvent()
+
+ eventProcessor.process(event, null)
+
+ assertNull(event.request.cookies)
+ }
+
+ @Test
+ fun `when sendDefaultPii is set to false, does not attach sensitive headers`() {
+ val request = MockMvcRequestBuilders
+ .get(URI.create("http://example.com?param1=xyz"))
+ .header("some-header", "some-header value")
+ .header("X-FORWARDED-FOR", "192.168.0.1")
+ .header("authorization", "Token")
+ .header("Authorization", "Token")
+ .header("Cookies", "some cookies")
+ .buildRequest(MockServletContext())
+ val sentryOptions = SentryOptions()
+ sentryOptions.isSendDefaultPii = false
+ val eventProcessor = SentryRequestHttpServletRequestProcessor(request, sentryOptions)
+ val event = SentryEvent()
+
+ eventProcessor.process(event, null)
+
+ assertFalse(event.request.headers.containsKey("X-FORWARDED-FOR"))
+ assertFalse(event.request.headers.containsKey("Authorization"))
+ assertFalse(event.request.headers.containsKey("authorization"))
+ assertFalse(event.request.headers.containsKey("Cookies"))
+ assertTrue(event.request.headers.containsKey("some-header"))
+ }
+}
diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt
new file mode 100644
index 000000000..40d726c81
--- /dev/null
+++ b/sentry-spring/src/test/kotlin/io/sentry/spring/SentrySpringIntegrationTest.kt
@@ -0,0 +1,152 @@
+package io.sentry.spring
+
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.reset
+import com.nhaarman.mockitokotlin2.verify
+import io.sentry.Sentry
+import io.sentry.test.checkEvent
+import io.sentry.transport.ITransport
+import java.lang.RuntimeException
+import org.assertj.core.api.Assertions.assertThat
+import org.awaitility.kotlin.await
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.boot.test.web.client.TestRestTemplate
+import org.springframework.boot.web.server.LocalServerPort
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.http.HttpEntity
+import org.springframework.http.HttpHeaders
+import org.springframework.http.HttpMethod
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.core.userdetails.UserDetails
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.crypto.factory.PasswordEncoderFactories
+import org.springframework.security.crypto.password.PasswordEncoder
+import org.springframework.security.provisioning.InMemoryUserDetailsManager
+import org.springframework.test.context.junit4.SpringRunner
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RestController
+
+@RunWith(SpringRunner::class)
+@SpringBootTest(
+ classes = [App::class],
+ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
+)
+class SentrySpringIntegrationTest {
+
+ @Autowired
+ lateinit var transport: ITransport
+
+ @LocalServerPort
+ lateinit var port: Integer
+
+ @Before
+ fun `reset mocks`() {
+ reset(transport)
+ }
+
+ @Test
+ fun `attaches request and user information to SentryEvents`() {
+ val restTemplate = TestRestTemplate().withBasicAuth("user", "password")
+ val headers = HttpHeaders()
+ headers["X-FORWARDED-FOR"] = listOf("169.128.0.1")
+ val entity = HttpEntity(headers)
+
+ restTemplate.exchange("http://localhost:$port/hello", HttpMethod.GET, entity, Void::class.java)
+
+ await.untilAsserted {
+ verify(transport).send(checkEvent { event ->
+ assertThat(event.request).isNotNull()
+ assertThat(event.request.url).isEqualTo("http://localhost:$port/hello")
+ assertThat(event.user).isNotNull()
+ assertThat(event.user.username).isEqualTo("user")
+ assertThat(event.user.ipAddress).isEqualTo("169.128.0.1")
+ })
+ }
+ }
+
+ @Test
+ fun `attaches first ip address if multiple addresses exist in a header`() {
+ val restTemplate = TestRestTemplate().withBasicAuth("user", "password")
+ val headers = HttpHeaders()
+ headers["X-FORWARDED-FOR"] = listOf("169.128.0.1, 192.168.0.1")
+ val entity = HttpEntity(headers)
+
+ restTemplate.exchange("http://localhost:$port/hello", HttpMethod.GET, entity, Void::class.java)
+
+ await.untilAsserted {
+ verify(transport).send(checkEvent { event ->
+ assertThat(event.user.ipAddress).isEqualTo("169.128.0.1")
+ })
+ }
+ }
+
+ @Test
+ fun `sends events for unhandled exceptions`() {
+ val restTemplate = TestRestTemplate().withBasicAuth("user", "password")
+
+ restTemplate.getForEntity("http://localhost:$port/throws", String::class.java)
+
+ await.untilAsserted {
+ verify(transport).send(checkEvent { event ->
+ assertThat(event.exceptions).isNotEmpty
+ val ex = event.exceptions.first()
+ assertThat(ex.value).isEqualTo("something went wrong")
+ assertThat(ex.mechanism.isHandled).isFalse()
+ })
+ }
+ }
+}
+
+@SpringBootApplication
+@EnableSentry(dsn = "http://key@localhost/proj", sendDefaultPii = true)
+open class App {
+
+ @Bean
+ open fun mockTransport() = mock()
+}
+
+@RestController
+class HelloController {
+
+ @GetMapping("/hello")
+ fun hello() {
+ Sentry.captureMessage("hello")
+ }
+
+ @GetMapping("/throws")
+ fun throws() {
+ throw RuntimeException("something went wrong")
+ }
+}
+
+@Configuration
+open class SecurityConfiguration : WebSecurityConfigurerAdapter() {
+
+ override fun configure(http: HttpSecurity) {
+ http.csrf().disable()
+ .authorizeRequests().anyRequest().authenticated()
+ .and()
+ .httpBasic()
+ }
+
+ @Bean
+ override fun userDetailsService(): UserDetailsService {
+ val encoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
+ val user: UserDetails = User
+ .builder()
+ .passwordEncoder { rawPassword -> encoder.encode(rawPassword) }
+ .username("user")
+ .password("password")
+ .roles("USER")
+ .build()
+ return InMemoryUserDetailsManager(user)
+ }
+}
diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/SentryUserProviderEventProcessorIntegrationTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/SentryUserProviderEventProcessorIntegrationTest.kt
new file mode 100644
index 000000000..6cfd87a35
--- /dev/null
+++ b/sentry-spring/src/test/kotlin/io/sentry/spring/SentryUserProviderEventProcessorIntegrationTest.kt
@@ -0,0 +1,93 @@
+package io.sentry.spring
+
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.reset
+import com.nhaarman.mockitokotlin2.verify
+import io.sentry.Sentry
+import io.sentry.SentryEvent
+import io.sentry.SentryOptions
+import io.sentry.protocol.User
+import io.sentry.test.checkEvent
+import io.sentry.transport.ITransport
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import org.assertj.core.api.Assertions.assertThat
+import org.awaitility.kotlin.await
+import org.springframework.boot.context.annotation.UserConfigurations
+import org.springframework.boot.test.context.runner.ApplicationContextRunner
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+
+class SentryUserProviderEventProcessorIntegrationTest {
+
+ @Test
+ fun `when SentryUserProvider bean is configured, sets user provided user data`() {
+ ApplicationContextRunner()
+ .withConfiguration(UserConfigurations.of(AppConfig::class.java, SentryUserProviderConfiguration::class.java))
+ .run {
+ val transport = it.getBean(ITransport::class.java)
+ reset(transport)
+
+ Sentry.captureMessage("test message")
+ await.untilAsserted {
+ verify(transport).send(checkEvent { event: SentryEvent ->
+ assertThat(event.user).isNotNull
+ assertThat(event.user.username).isEqualTo("john.smith")
+ })
+ }
+ }
+ }
+
+ @Test
+ fun `when SentryUserProvider bean is not configured, user data is not set`() {
+ ApplicationContextRunner()
+ .withConfiguration(UserConfigurations.of(AppConfig::class.java))
+ .run {
+ val transport = it.getBean(ITransport::class.java)
+ reset(transport)
+
+ Sentry.captureMessage("test message")
+ await.untilAsserted {
+ verify(transport).send(checkEvent { event: SentryEvent ->
+ assertThat(event.user).isNull()
+ })
+ }
+ }
+ }
+
+ @Test
+ fun `when custom SentryUserProvider bean is configured, it's added after HttpServletRequestSentryUserProvider`() {
+ ApplicationContextRunner()
+ .withConfiguration(UserConfigurations.of(AppConfig::class.java, SentryUserProviderConfiguration::class.java))
+ .run {
+ val options = it.getBean(SentryOptions::class.java)
+ val userProviderEventProcessors = options.eventProcessors.filterIsInstance()
+ assertEquals(2, userProviderEventProcessors.size)
+ assertTrue(userProviderEventProcessors[0].sentryUserProvider is HttpServletRequestSentryUserProvider)
+ assertTrue(userProviderEventProcessors[1].sentryUserProvider is CustomSentryUserProvider)
+ }
+ }
+
+ @EnableSentry(dsn = "http://key@localhost/proj")
+ open class AppConfig {
+
+ @Bean
+ open fun mockTransport() = mock()
+ }
+
+ @Configuration
+ open class SentryUserProviderConfiguration {
+
+ @Bean
+ open fun userProvider() = CustomSentryUserProvider()
+ }
+
+ open class CustomSentryUserProvider : SentryUserProvider {
+ override fun provideUser(): User? {
+ val user = User()
+ user.username = "john.smith"
+ return user
+ }
+ }
+}
diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/SentryUserProviderEventProcessorTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/SentryUserProviderEventProcessorTest.kt
new file mode 100644
index 000000000..5694f52ab
--- /dev/null
+++ b/sentry-spring/src/test/kotlin/io/sentry/spring/SentryUserProviderEventProcessorTest.kt
@@ -0,0 +1,130 @@
+package io.sentry.spring
+
+import io.sentry.SentryEvent
+import io.sentry.protocol.User
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+
+class SentryUserProviderEventProcessorTest {
+
+ @Test
+ fun `when event user is null, provider user data is set`() {
+
+ val processor = SentryUserProviderEventProcessor {
+ val user = User()
+ user.username = "john.doe"
+ user.id = "user-id"
+ user.ipAddress = "192.168.0.1"
+ user.email = "john.doe@example.com"
+ user.others = mapOf("key" to "value")
+ user
+ }
+
+ val event = SentryEvent()
+ val result = processor.process(event, null)
+
+ assertNotNull(result)
+ assertNotNull(result.user)
+ assertEquals("john.doe", result.user.username)
+ assertEquals("user-id", result.user.id)
+ assertEquals("192.168.0.1", result.user.ipAddress)
+ assertEquals("john.doe@example.com", result.user.email)
+ assertEquals(mapOf("key" to "value"), result.user.others)
+ }
+
+ @Test
+ fun `when event user is empty, provider user data is set`() {
+ val processor = SentryUserProviderEventProcessor {
+ val user = User()
+ user.username = "john.doe"
+ user.id = "user-id"
+ user.ipAddress = "192.168.0.1"
+ user.email = "john.doe@example.com"
+ user.others = mapOf("key" to "value")
+ user
+ }
+
+ val event = SentryEvent()
+ event.user = User()
+ val result = processor.process(event, null)
+
+ assertNotNull(result)
+ assertNotNull(result.user)
+ assertEquals("john.doe", result.user.username)
+ assertEquals("user-id", result.user.id)
+ assertEquals("192.168.0.1", result.user.ipAddress)
+ assertEquals("john.doe@example.com", result.user.email)
+ assertEquals(mapOf("key" to "value"), result.user.others)
+ }
+
+ @Test
+ fun `when processor returns empty User, user data is not changed`() {
+ val processor = SentryUserProviderEventProcessor {
+ val user = User()
+ user
+ }
+
+ val event = SentryEvent()
+ event.user = User()
+ event.user.username = "jane.smith"
+ event.user.id = "jane-smith"
+ event.user.ipAddress = "192.168.0.3"
+ event.user.email = "jane.smith@example.com"
+ event.user.others = mapOf("key" to "value")
+
+ val result = processor.process(event, null)
+
+ assertNotNull(result)
+ assertNotNull(result.user)
+ assertEquals("jane.smith", result.user.username)
+ assertEquals("jane-smith", result.user.id)
+ assertEquals("192.168.0.3", result.user.ipAddress)
+ assertEquals("jane.smith@example.com", result.user.email)
+ assertEquals(mapOf("key" to "value"), result.user.others)
+ }
+
+ @Test
+ fun `when processor returns null, user data is not changed`() {
+ val processor = SentryUserProviderEventProcessor {
+ null
+ }
+
+ val event = SentryEvent()
+ event.user = User()
+ event.user.username = "jane.smith"
+ event.user.id = "jane-smith"
+ event.user.ipAddress = "192.168.0.3"
+ event.user.email = "jane.smith@example.com"
+ event.user.others = mapOf("key" to "value")
+
+ val result = processor.process(event, null)
+
+ assertNotNull(result)
+ assertNotNull(result.user)
+ assertEquals("jane.smith", result.user.username)
+ assertEquals("jane-smith", result.user.id)
+ assertEquals("192.168.0.3", result.user.ipAddress)
+ assertEquals("jane.smith@example.com", result.user.email)
+ assertEquals(mapOf("key" to "value"), result.user.others)
+ }
+
+ @Test
+ fun `merges user#others with existing user#others set on SentryEvent`() {
+ val processor = SentryUserProviderEventProcessor {
+ val user = User()
+ user.others = mapOf("key" to "value")
+ user
+ }
+
+ val event = SentryEvent()
+ event.user = User()
+ event.user.others = mapOf("new-key" to "new-value")
+
+ val result = processor.process(event, null)
+
+ assertNotNull(result)
+ assertNotNull(result.user)
+ assertEquals(mapOf("key" to "value", "new-key" to "new-value"), result.user.others)
+ }
+}
diff --git a/sentry-test-support/build.gradle.kts b/sentry-test-support/build.gradle.kts
new file mode 100644
index 000000000..a2821878e
--- /dev/null
+++ b/sentry-test-support/build.gradle.kts
@@ -0,0 +1,39 @@
+plugins {
+ `java-library`
+ kotlin("jvm")
+ jacoco
+ id(Config.QualityPlugins.errorProne)
+ id(Config.QualityPlugins.gradleVersions)
+}
+
+configure {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+tasks.withType().configureEach {
+ kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
+}
+
+dependencies {
+ api(project(":sentry"))
+ // Envelopes require JSON. Until a parse is done without GSON, we'll depend on it explicitly here
+ implementation(Config.Libs.gson)
+
+ compileOnly(Config.CompileOnly.nopen)
+ errorprone(Config.CompileOnly.nopenChecker)
+ errorprone(Config.CompileOnly.errorprone)
+ errorproneJavac(Config.CompileOnly.errorProneJavac8)
+ compileOnly(Config.CompileOnly.jetbrainsAnnotations)
+
+ // tests
+ implementation(kotlin(Config.kotlinStdLib))
+ implementation(Config.TestLibs.kotlinTestJunit)
+ implementation(Config.TestLibs.mockitoKotlin)
+}
+
+configure {
+ test {
+ java.srcDir("src/test/java")
+ }
+}
diff --git a/sentry-test-support/src/main/kotlin/io/sentry/test/assertions.kt b/sentry-test-support/src/main/kotlin/io/sentry/test/assertions.kt
new file mode 100644
index 000000000..4b8ca5971
--- /dev/null
+++ b/sentry-test-support/src/main/kotlin/io/sentry/test/assertions.kt
@@ -0,0 +1,21 @@
+package io.sentry.test
+
+import com.nhaarman.mockitokotlin2.check
+import io.sentry.GsonSerializer
+import io.sentry.NoOpLogger
+import io.sentry.SentryEnvelope
+import io.sentry.SentryEvent
+import io.sentry.SentryOptions
+
+/**
+ * Verifies is [SentryEnvelope] contains first event matching a predicate.
+ */
+inline fun checkEvent(noinline predicate: (SentryEvent) -> Unit): SentryEnvelope {
+ val options = SentryOptions().apply {
+ setSerializer(GsonSerializer(NoOpLogger.getInstance(), envelopeReader))
+ }
+ return check {
+ val event = it.items.first().getEvent(options.serializer)!!
+ predicate(event)
+ }
+}
diff --git a/sentry-core/build.gradle.kts b/sentry/build.gradle.kts
similarity index 90%
rename from sentry-core/build.gradle.kts
rename to sentry/build.gradle.kts
index 33d152971..3dd509f5b 100644
--- a/sentry-core/build.gradle.kts
+++ b/sentry/build.gradle.kts
@@ -44,7 +44,7 @@ configure {
}
jacoco {
- toolVersion = Config.QualityPlugins.jacocoVersion
+ toolVersion = Config.QualityPlugins.Jacoco.version
}
tasks.jacocoTestReport {
@@ -57,8 +57,7 @@ tasks.jacocoTestReport {
tasks {
jacocoTestCoverageVerification {
violationRules {
- // TODO: Raise the minimum to a sensible value.
- rule { limit { minimum = BigDecimal.valueOf(0.1) } }
+ rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } }
}
}
check {
@@ -69,7 +68,7 @@ tasks {
buildConfig {
useJavaOutput()
- packageName("io.sentry.core")
+ packageName("io.sentry")
buildConfigField("String", "SENTRY_JAVA_SDK_NAME", "\"${Config.Sentry.SENTRY_JAVA_SDK_NAME}\"")
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
}
@@ -79,7 +78,7 @@ tasks.withType().configureEach {
dependsOn(generateBuildConfig)
}
-//TODO: move thse blocks to parent gradle file, DRY
+// TODO: move thse blocks to parent gradle file, DRY
configure {
userOrg = Config.Sentry.userOrg
groupId = project.group.toString()
@@ -99,6 +98,5 @@ configure {
devEmail = Config.Sentry.devEmail
scmConnection = Config.Sentry.scmConnection
scmDevConnection = Config.Sentry.scmDevConnection
- scmUrl = Config.Sentry.scmUrl
+ scmUrl = Config.Sentry.scmUrl
}
-
diff --git a/sentry-core/src/main/java/io/sentry/core/AsyncConnectionFactory.java b/sentry/src/main/java/io/sentry/AsyncConnectionFactory.java
similarity index 55%
rename from sentry-core/src/main/java/io/sentry/core/AsyncConnectionFactory.java
rename to sentry/src/main/java/io/sentry/AsyncConnectionFactory.java
index e7c2cf483..663a4f0a0 100644
--- a/sentry-core/src/main/java/io/sentry/core/AsyncConnectionFactory.java
+++ b/sentry/src/main/java/io/sentry/AsyncConnectionFactory.java
@@ -1,22 +1,19 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.cache.IEnvelopeCache;
-import io.sentry.core.cache.IEventCache;
-import io.sentry.core.transport.AsyncConnection;
+import io.sentry.cache.IEnvelopeCache;
+import io.sentry.transport.AsyncConnection;
final class AsyncConnectionFactory {
private AsyncConnectionFactory() {}
- public static AsyncConnection create(
- SentryOptions options, IEventCache eventCache, IEnvelopeCache sessionCache) {
+ public static AsyncConnection create(SentryOptions options, IEnvelopeCache envelopeCache) {
// the connection doesn't do any retries of failed sends and can hold at most the same number
// of pending events as there are being cached. The rest is dropped.
return new AsyncConnection(
options.getTransport(),
options.getTransportGate(),
- eventCache,
- sessionCache,
+ envelopeCache,
options.getMaxQueueSize(),
options);
}
diff --git a/sentry-core/src/main/java/io/sentry/core/Breadcrumb.java b/sentry/src/main/java/io/sentry/Breadcrumb.java
similarity index 89%
rename from sentry-core/src/main/java/io/sentry/core/Breadcrumb.java
rename to sentry/src/main/java/io/sentry/Breadcrumb.java
index cfe7c8f35..b428dd650 100644
--- a/sentry-core/src/main/java/io/sentry/core/Breadcrumb.java
+++ b/sentry/src/main/java/io/sentry/Breadcrumb.java
@@ -1,6 +1,6 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.util.CollectionUtils;
+import io.sentry.util.CollectionUtils;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
@@ -43,6 +43,22 @@ public final class Breadcrumb implements Cloneable, IUnknownPropertiesConsumer {
this.timestamp = timestamp;
}
+ /**
+ * Creates HTTP breadcrumb.
+ *
+ * @param url - the request URL
+ * @param method - the request method
+ * @return the breadcrumb
+ */
+ public static @NotNull Breadcrumb http(final @NotNull String url, final @NotNull String method) {
+ final Breadcrumb breadcrumb = new Breadcrumb();
+ breadcrumb.setType("http");
+ breadcrumb.setCategory("http");
+ breadcrumb.setData("url", url);
+ breadcrumb.setData("method", method.toUpperCase(Locale.getDefault()));
+ return breadcrumb;
+ }
+
/** Breadcrumb ctor */
public Breadcrumb() {
this(DateUtils.getCurrentDateTimeOrNull());
diff --git a/sentry-core/src/main/java/io/sentry/core/CircularFifoQueue.java b/sentry/src/main/java/io/sentry/CircularFifoQueue.java
similarity index 99%
rename from sentry-core/src/main/java/io/sentry/core/CircularFifoQueue.java
rename to sentry/src/main/java/io/sentry/CircularFifoQueue.java
index 46ae914f3..60a1df35a 100644
--- a/sentry-core/src/main/java/io/sentry/core/CircularFifoQueue.java
+++ b/sentry/src/main/java/io/sentry/CircularFifoQueue.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import java.io.IOException;
import java.io.ObjectInputStream;
diff --git a/sentry-core/src/main/java/io/sentry/core/CredentialsSettingConfigurator.java b/sentry/src/main/java/io/sentry/CredentialsSettingConfigurator.java
similarity index 93%
rename from sentry-core/src/main/java/io/sentry/core/CredentialsSettingConfigurator.java
rename to sentry/src/main/java/io/sentry/CredentialsSettingConfigurator.java
index 2d244c057..32b9faf33 100644
--- a/sentry-core/src/main/java/io/sentry/core/CredentialsSettingConfigurator.java
+++ b/sentry/src/main/java/io/sentry/CredentialsSettingConfigurator.java
@@ -1,6 +1,6 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.transport.IConnectionConfigurator;
+import io.sentry.transport.IConnectionConfigurator;
import java.net.HttpURLConnection;
/**
diff --git a/sentry-core/src/main/java/io/sentry/core/DateUtils.java b/sentry/src/main/java/io/sentry/DateUtils.java
similarity index 99%
rename from sentry-core/src/main/java/io/sentry/core/DateUtils.java
rename to sentry/src/main/java/io/sentry/DateUtils.java
index 2110ea896..7670c8b01 100644
--- a/sentry-core/src/main/java/io/sentry/core/DateUtils.java
+++ b/sentry/src/main/java/io/sentry/DateUtils.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import java.text.DateFormat;
import java.text.ParseException;
diff --git a/sentry-core/src/main/java/io/sentry/core/DiagnosticLogger.java b/sentry/src/main/java/io/sentry/DiagnosticLogger.java
similarity index 97%
rename from sentry-core/src/main/java/io/sentry/core/DiagnosticLogger.java
rename to sentry/src/main/java/io/sentry/DiagnosticLogger.java
index 327e7bd00..c48f79d95 100644
--- a/sentry-core/src/main/java/io/sentry/core/DiagnosticLogger.java
+++ b/sentry/src/main/java/io/sentry/DiagnosticLogger.java
@@ -1,6 +1,6 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.util.Objects;
+import io.sentry.util.Objects;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
diff --git a/sentry-core/src/main/java/io/sentry/core/DirectoryProcessor.java b/sentry/src/main/java/io/sentry/DirectoryProcessor.java
similarity index 79%
rename from sentry-core/src/main/java/io/sentry/core/DirectoryProcessor.java
rename to sentry/src/main/java/io/sentry/DirectoryProcessor.java
index 578d1aef9..cdd47b3f5 100644
--- a/sentry-core/src/main/java/io/sentry/core/DirectoryProcessor.java
+++ b/sentry/src/main/java/io/sentry/DirectoryProcessor.java
@@ -1,11 +1,11 @@
-package io.sentry.core;
+package io.sentry;
-import static io.sentry.core.SentryLevel.ERROR;
+import static io.sentry.SentryLevel.ERROR;
-import io.sentry.core.hints.Cached;
-import io.sentry.core.hints.Flushable;
-import io.sentry.core.hints.Retryable;
-import io.sentry.core.hints.SubmissionResult;
+import io.sentry.hints.Cached;
+import io.sentry.hints.Flushable;
+import io.sentry.hints.Retryable;
+import io.sentry.hints.SubmissionResult;
import java.io.File;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -22,7 +22,7 @@ abstract class DirectoryProcessor {
this.flushTimeoutMillis = flushTimeoutMillis;
}
- public void processDirectory(@NotNull File directory) {
+ public void processDirectory(final @NotNull File directory) {
try {
logger.log(SentryLevel.DEBUG, "Processing dir. %s", directory.getAbsolutePath());
@@ -39,13 +39,13 @@ public void processDirectory(@NotNull File directory) {
return;
}
- File[] listFiles = directory.listFiles();
+ final File[] listFiles = directory.listFiles();
if (listFiles == null) {
logger.log(SentryLevel.ERROR, "Cache dir %s is null.", directory.getAbsolutePath());
return;
}
- File[] filteredListFiles = directory.listFiles((d, name) -> isRelevantFileName(name));
+ final File[] filteredListFiles = directory.listFiles((d, name) -> isRelevantFileName(name));
logger.log(
SentryLevel.DEBUG,
@@ -62,7 +62,7 @@ public void processDirectory(@NotNull File directory) {
logger.log(SentryLevel.DEBUG, "Processing file: %s", file.getAbsolutePath());
- final SendCachedEventHint hint = new SendCachedEventHint(flushTimeoutMillis, logger);
+ final SendCachedEnvelopeHint hint = new SendCachedEnvelopeHint(flushTimeoutMillis, logger);
processFile(file, hint);
}
} catch (Exception e) {
@@ -70,11 +70,11 @@ public void processDirectory(@NotNull File directory) {
}
}
- protected abstract void processFile(File file, @Nullable Object hint);
+ protected abstract void processFile(final @NotNull File file, final @Nullable Object hint);
protected abstract boolean isRelevantFileName(String fileName);
- private static final class SendCachedEventHint
+ private static final class SendCachedEnvelopeHint
implements Cached, Retryable, SubmissionResult, Flushable {
boolean retry = false;
boolean succeeded = false;
@@ -83,7 +83,7 @@ private static final class SendCachedEventHint
private final long flushTimeoutMillis;
private final @NotNull ILogger logger;
- public SendCachedEventHint(final long flushTimeoutMillis, final @NotNull ILogger logger) {
+ public SendCachedEnvelopeHint(final long flushTimeoutMillis, final @NotNull ILogger logger) {
this.flushTimeoutMillis = flushTimeoutMillis;
this.latch = new CountDownLatch(1);
this.logger = logger;
diff --git a/sentry-core/src/main/java/io/sentry/core/Dsn.java b/sentry/src/main/java/io/sentry/Dsn.java
similarity index 98%
rename from sentry-core/src/main/java/io/sentry/core/Dsn.java
rename to sentry/src/main/java/io/sentry/Dsn.java
index d686a83a0..1fc0d15a3 100644
--- a/sentry-core/src/main/java/io/sentry/core/Dsn.java
+++ b/sentry/src/main/java/io/sentry/Dsn.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import java.net.URI;
import org.jetbrains.annotations.Nullable;
diff --git a/sentry/src/main/java/io/sentry/DuplicateEventDetectionEventProcessor.java b/sentry/src/main/java/io/sentry/DuplicateEventDetectionEventProcessor.java
new file mode 100644
index 000000000..686ccbf45
--- /dev/null
+++ b/sentry/src/main/java/io/sentry/DuplicateEventDetectionEventProcessor.java
@@ -0,0 +1,75 @@
+package io.sentry;
+
+import io.sentry.exception.ExceptionMechanismException;
+import io.sentry.util.Objects;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Deduplicates events containing throwable that has been already processed. */
+public final class DuplicateEventDetectionEventProcessor implements EventProcessor {
+ private final WeakHashMap capturedObjects = new WeakHashMap<>();
+ private final SentryOptions options;
+
+ public DuplicateEventDetectionEventProcessor(final @NotNull SentryOptions options) {
+ this.options = Objects.requireNonNull(options, "options are required");
+ }
+
+ @Override
+ public SentryEvent process(final @NotNull SentryEvent event, final @Nullable Object hint) {
+ final Throwable throwable = event.getThrowable();
+ if (throwable != null) {
+ if (throwable instanceof ExceptionMechanismException) {
+ final ExceptionMechanismException ex = (ExceptionMechanismException) throwable;
+ if (capturedObjects.containsKey(ex.getThrowable())) {
+ options
+ .getLogger()
+ .log(
+ SentryLevel.DEBUG,
+ "Duplicate Exception detected. Event %s will be discarded.",
+ event.getEventId());
+ return null;
+ } else {
+ capturedObjects.put(ex.getThrowable(), null);
+ }
+ } else {
+ if (capturedObjects.containsKey(throwable)
+ || containsAnyKey(capturedObjects, allCauses(throwable))) {
+ options
+ .getLogger()
+ .log(
+ SentryLevel.DEBUG,
+ "Duplicate Exception detected. Event %s will be discarded.",
+ event.getEventId());
+ return null;
+ } else {
+ capturedObjects.put(throwable, null);
+ }
+ }
+ }
+ return event;
+ }
+
+ private static boolean containsAnyKey(
+ final @NotNull Map map, final @NotNull List list) {
+ for (T entry : list) {
+ if (map.containsKey(entry)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static @NotNull List allCauses(final @NotNull Throwable throwable) {
+ final List causes = new ArrayList<>();
+ Throwable ex = throwable;
+ while (ex.getCause() != null) {
+ causes.add(ex.getCause());
+ ex = ex.getCause();
+ }
+ return causes;
+ }
+}
diff --git a/sentry-core/src/main/java/io/sentry/core/EnvelopeReader.java b/sentry/src/main/java/io/sentry/EnvelopeReader.java
similarity index 99%
rename from sentry-core/src/main/java/io/sentry/core/EnvelopeReader.java
rename to sentry/src/main/java/io/sentry/EnvelopeReader.java
index 5ffec841c..6ec6d6059 100644
--- a/sentry-core/src/main/java/io/sentry/core/EnvelopeReader.java
+++ b/sentry/src/main/java/io/sentry/EnvelopeReader.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
diff --git a/sentry-core/src/main/java/io/sentry/core/SendCachedEvent.java b/sentry/src/main/java/io/sentry/EnvelopeSender.java
similarity index 68%
rename from sentry-core/src/main/java/io/sentry/core/SendCachedEvent.java
rename to sentry/src/main/java/io/sentry/EnvelopeSender.java
index 13f8480d4..26b4ab771 100644
--- a/sentry-core/src/main/java/io/sentry/core/SendCachedEvent.java
+++ b/sentry/src/main/java/io/sentry/EnvelopeSender.java
@@ -1,35 +1,35 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.cache.DiskCache;
-import io.sentry.core.hints.Flushable;
-import io.sentry.core.hints.Retryable;
-import io.sentry.core.util.LogUtils;
-import io.sentry.core.util.Objects;
-import java.io.BufferedReader;
+import io.sentry.cache.EnvelopeCache;
+import io.sentry.hints.Flushable;
+import io.sentry.hints.Retryable;
+import io.sentry.util.LogUtils;
+import io.sentry.util.Objects;
+import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.nio.charset.Charset;
+import java.io.InputStream;
+import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-final class SendCachedEvent extends DirectoryProcessor {
- private static final Charset UTF_8 = Charset.forName("UTF-8");
- private final ISerializer serializer;
- private final IHub hub;
+@ApiStatus.Internal
+public final class EnvelopeSender extends DirectoryProcessor implements IEnvelopeSender {
+
+ private final @NotNull IHub hub;
+ private final @NotNull ISerializer serializer;
private final @NotNull ILogger logger;
- SendCachedEvent(
- @NotNull ISerializer serializer,
- @NotNull IHub hub,
+ public EnvelopeSender(
+ final @NotNull IHub hub,
+ final @NotNull ISerializer serializer,
final @NotNull ILogger logger,
final long flushTimeoutMillis) {
super(logger, flushTimeoutMillis);
- this.serializer = Objects.requireNonNull(serializer, "Serializer is required.");
this.hub = Objects.requireNonNull(hub, "Hub is required.");
+ this.serializer = Objects.requireNonNull(serializer, "Serializer is required.");
this.logger = Objects.requireNonNull(logger, "Logger is required.");
}
@@ -49,27 +49,18 @@ protected void processFile(@NotNull File file, @Nullable Object hint) {
if (!file.getParentFile().canWrite()) {
logger.log(
SentryLevel.WARNING,
- "File '%s' cannot be delete so it will not be processed.",
+ "File '%s' cannot be deleted so it will not be processed.",
file.getAbsolutePath());
return;
}
- try (final Reader reader =
- new BufferedReader(new InputStreamReader(new FileInputStream(file), UTF_8))) {
- SentryEvent event = serializer.deserializeEvent(reader);
- hub.captureEvent(event, hint);
+ try (final InputStream is = new BufferedInputStream(new FileInputStream(file))) {
+ SentryEnvelope envelope = serializer.deserializeEnvelope(is);
+ hub.captureEnvelope(envelope, hint);
if (hint instanceof Flushable) {
if (!((Flushable) hint).waitFlush()) {
- logger.log(
- SentryLevel.WARNING,
- "Timed out waiting for event submission: %s",
- event.getEventId());
-
- // TODO: find out about the time out
- // if (hint instanceof Retryable) {
- // ((Retryable) hint).setRetry(true);
- // }
+ logger.log(SentryLevel.WARNING, "Timed out waiting for envelope submission.");
}
} else {
LogUtils.logIfNotFlushable(logger, hint);
@@ -79,7 +70,8 @@ protected void processFile(@NotNull File file, @Nullable Object hint) {
} catch (IOException e) {
logger.log(SentryLevel.ERROR, e, "I/O on file '%s' failed.", file.getAbsolutePath());
} catch (Exception e) {
- logger.log(SentryLevel.ERROR, e, "Failed to capture cached event %s", file.getAbsolutePath());
+ logger.log(
+ SentryLevel.ERROR, e, "Failed to capture cached envelope %s", file.getAbsolutePath());
if (hint instanceof Retryable) {
((Retryable) hint).setRetry(false);
logger.log(SentryLevel.INFO, e, "File '%s' won't retry.", file.getAbsolutePath());
@@ -106,7 +98,14 @@ protected void processFile(@NotNull File file, @Nullable Object hint) {
@Override
protected boolean isRelevantFileName(String fileName) {
- return fileName.endsWith(DiskCache.FILE_SUFFIX);
+ return fileName.endsWith(EnvelopeCache.SUFFIX_ENVELOPE_FILE);
+ }
+
+ @Override
+ public void processEnvelopeFile(@NotNull String path, @Nullable Object hint) {
+ Objects.requireNonNull(path, "Path is required.");
+
+ processFile(new File(path), hint);
}
private void safeDelete(File file, String errorMessageSuffix) {
diff --git a/sentry-core/src/main/java/io/sentry/core/EventProcessor.java b/sentry/src/main/java/io/sentry/EventProcessor.java
similarity index 82%
rename from sentry-core/src/main/java/io/sentry/core/EventProcessor.java
rename to sentry/src/main/java/io/sentry/EventProcessor.java
index 6a809b29f..107cbd715 100644
--- a/sentry-core/src/main/java/io/sentry/core/EventProcessor.java
+++ b/sentry/src/main/java/io/sentry/EventProcessor.java
@@ -1,7 +1,8 @@
-package io.sentry.core;
+package io.sentry;
import org.jetbrains.annotations.Nullable;
public interface EventProcessor {
+ @Nullable
SentryEvent process(SentryEvent event, @Nullable Object hint);
}
diff --git a/sentry-core/src/main/java/io/sentry/core/GsonSerializer.java b/sentry/src/main/java/io/sentry/GsonSerializer.java
similarity index 88%
rename from sentry-core/src/main/java/io/sentry/core/GsonSerializer.java
rename to sentry/src/main/java/io/sentry/GsonSerializer.java
index 4d44fa523..3a2ec985e 100644
--- a/sentry-core/src/main/java/io/sentry/core/GsonSerializer.java
+++ b/sentry/src/main/java/io/sentry/GsonSerializer.java
@@ -1,24 +1,24 @@
-package io.sentry.core;
+package io.sentry;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
-import io.sentry.core.adapters.ContextsDeserializerAdapter;
-import io.sentry.core.adapters.ContextsSerializerAdapter;
-import io.sentry.core.adapters.DateDeserializerAdapter;
-import io.sentry.core.adapters.DateSerializerAdapter;
-import io.sentry.core.adapters.OrientationDeserializerAdapter;
-import io.sentry.core.adapters.OrientationSerializerAdapter;
-import io.sentry.core.adapters.SentryIdDeserializerAdapter;
-import io.sentry.core.adapters.SentryIdSerializerAdapter;
-import io.sentry.core.adapters.SentryLevelDeserializerAdapter;
-import io.sentry.core.adapters.SentryLevelSerializerAdapter;
-import io.sentry.core.adapters.TimeZoneDeserializerAdapter;
-import io.sentry.core.adapters.TimeZoneSerializerAdapter;
-import io.sentry.core.protocol.Contexts;
-import io.sentry.core.protocol.Device;
-import io.sentry.core.protocol.SentryId;
-import io.sentry.core.util.Objects;
+import io.sentry.adapters.ContextsDeserializerAdapter;
+import io.sentry.adapters.ContextsSerializerAdapter;
+import io.sentry.adapters.DateDeserializerAdapter;
+import io.sentry.adapters.DateSerializerAdapter;
+import io.sentry.adapters.OrientationDeserializerAdapter;
+import io.sentry.adapters.OrientationSerializerAdapter;
+import io.sentry.adapters.SentryIdDeserializerAdapter;
+import io.sentry.adapters.SentryIdSerializerAdapter;
+import io.sentry.adapters.SentryLevelDeserializerAdapter;
+import io.sentry.adapters.SentryLevelSerializerAdapter;
+import io.sentry.adapters.TimeZoneDeserializerAdapter;
+import io.sentry.adapters.TimeZoneSerializerAdapter;
+import io.sentry.protocol.Contexts;
+import io.sentry.protocol.Device;
+import io.sentry.protocol.SentryId;
+import io.sentry.util.Objects;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
diff --git a/sentry-core/src/main/java/io/sentry/core/HttpTransportFactory.java b/sentry/src/main/java/io/sentry/HttpTransportFactory.java
similarity index 83%
rename from sentry-core/src/main/java/io/sentry/core/HttpTransportFactory.java
rename to sentry/src/main/java/io/sentry/HttpTransportFactory.java
index 29a457c0e..7f902498e 100644
--- a/sentry-core/src/main/java/io/sentry/core/HttpTransportFactory.java
+++ b/sentry/src/main/java/io/sentry/HttpTransportFactory.java
@@ -1,8 +1,8 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.transport.HttpTransport;
-import io.sentry.core.transport.IConnectionConfigurator;
-import io.sentry.core.transport.ITransport;
+import io.sentry.transport.HttpTransport;
+import io.sentry.transport.IConnectionConfigurator;
+import io.sentry.transport.ITransport;
import java.net.MalformedURLException;
import java.net.URL;
import org.jetbrains.annotations.NotNull;
diff --git a/sentry-core/src/main/java/io/sentry/core/Hub.java b/sentry/src/main/java/io/sentry/Hub.java
similarity index 98%
rename from sentry-core/src/main/java/io/sentry/core/Hub.java
rename to sentry/src/main/java/io/sentry/Hub.java
index 44421fdaa..69d4c112a 100644
--- a/sentry-core/src/main/java/io/sentry/core/Hub.java
+++ b/sentry/src/main/java/io/sentry/Hub.java
@@ -1,10 +1,10 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.hints.SessionEndHint;
-import io.sentry.core.hints.SessionStartHint;
-import io.sentry.core.protocol.SentryId;
-import io.sentry.core.protocol.User;
-import io.sentry.core.util.Objects;
+import io.sentry.hints.SessionEndHint;
+import io.sentry.hints.SessionStartHint;
+import io.sentry.protocol.SentryId;
+import io.sentry.protocol.User;
+import io.sentry.util.Objects;
import java.io.Closeable;
import java.util.Deque;
import java.util.List;
diff --git a/sentry-core/src/main/java/io/sentry/core/HubAdapter.java b/sentry/src/main/java/io/sentry/HubAdapter.java
similarity index 96%
rename from sentry-core/src/main/java/io/sentry/core/HubAdapter.java
rename to sentry/src/main/java/io/sentry/HubAdapter.java
index 6bf16abc3..ed4c0b474 100644
--- a/sentry-core/src/main/java/io/sentry/core/HubAdapter.java
+++ b/sentry/src/main/java/io/sentry/HubAdapter.java
@@ -1,7 +1,7 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.protocol.SentryId;
-import io.sentry.core.protocol.User;
+import io.sentry.protocol.SentryId;
+import io.sentry.protocol.User;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
diff --git a/sentry-core/src/main/java/io/sentry/core/IEnvelopeReader.java b/sentry/src/main/java/io/sentry/IEnvelopeReader.java
similarity index 91%
rename from sentry-core/src/main/java/io/sentry/core/IEnvelopeReader.java
rename to sentry/src/main/java/io/sentry/IEnvelopeReader.java
index d97f6fbd6..145b63729 100644
--- a/sentry-core/src/main/java/io/sentry/core/IEnvelopeReader.java
+++ b/sentry/src/main/java/io/sentry/IEnvelopeReader.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import java.io.IOException;
import java.io.InputStream;
diff --git a/sentry-core/src/main/java/io/sentry/core/IEnvelopeSender.java b/sentry/src/main/java/io/sentry/IEnvelopeSender.java
similarity index 89%
rename from sentry-core/src/main/java/io/sentry/core/IEnvelopeSender.java
rename to sentry/src/main/java/io/sentry/IEnvelopeSender.java
index 5af52c195..2e203e039 100644
--- a/sentry-core/src/main/java/io/sentry/core/IEnvelopeSender.java
+++ b/sentry/src/main/java/io/sentry/IEnvelopeSender.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
diff --git a/sentry-core/src/main/java/io/sentry/core/IHub.java b/sentry/src/main/java/io/sentry/IHub.java
similarity index 98%
rename from sentry-core/src/main/java/io/sentry/core/IHub.java
rename to sentry/src/main/java/io/sentry/IHub.java
index 1b71b994b..9f0369d88 100644
--- a/sentry-core/src/main/java/io/sentry/core/IHub.java
+++ b/sentry/src/main/java/io/sentry/IHub.java
@@ -1,7 +1,7 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.protocol.SentryId;
-import io.sentry.core.protocol.User;
+import io.sentry.protocol.SentryId;
+import io.sentry.protocol.User;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
diff --git a/sentry-core/src/main/java/io/sentry/core/ILogger.java b/sentry/src/main/java/io/sentry/ILogger.java
similarity index 97%
rename from sentry-core/src/main/java/io/sentry/core/ILogger.java
rename to sentry/src/main/java/io/sentry/ILogger.java
index 08e8e4a2f..09804e81a 100644
--- a/sentry-core/src/main/java/io/sentry/core/ILogger.java
+++ b/sentry/src/main/java/io/sentry/ILogger.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
/** Sentry SDK internal logging interface. */
public interface ILogger {
diff --git a/sentry-core/src/main/java/io/sentry/core/ISentryClient.java b/sentry/src/main/java/io/sentry/ISentryClient.java
similarity index 98%
rename from sentry-core/src/main/java/io/sentry/core/ISentryClient.java
rename to sentry/src/main/java/io/sentry/ISentryClient.java
index 0fc363fe1..e41efec32 100644
--- a/sentry-core/src/main/java/io/sentry/core/ISentryClient.java
+++ b/sentry/src/main/java/io/sentry/ISentryClient.java
@@ -1,7 +1,7 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.protocol.Message;
-import io.sentry.core.protocol.SentryId;
+import io.sentry.protocol.Message;
+import io.sentry.protocol.SentryId;
import org.jetbrains.annotations.Nullable;
/** Sentry Client interface */
diff --git a/sentry-core/src/main/java/io/sentry/core/ISentryExecutorService.java b/sentry/src/main/java/io/sentry/ISentryExecutorService.java
similarity index 95%
rename from sentry-core/src/main/java/io/sentry/core/ISentryExecutorService.java
rename to sentry/src/main/java/io/sentry/ISentryExecutorService.java
index b096ce7b6..1f54c388b 100644
--- a/sentry-core/src/main/java/io/sentry/core/ISentryExecutorService.java
+++ b/sentry/src/main/java/io/sentry/ISentryExecutorService.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import java.util.concurrent.Future;
diff --git a/sentry-core/src/main/java/io/sentry/core/ISerializer.java b/sentry/src/main/java/io/sentry/ISerializer.java
similarity index 95%
rename from sentry-core/src/main/java/io/sentry/core/ISerializer.java
rename to sentry/src/main/java/io/sentry/ISerializer.java
index b9664472f..8b8921644 100644
--- a/sentry-core/src/main/java/io/sentry/core/ISerializer.java
+++ b/sentry/src/main/java/io/sentry/ISerializer.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import java.io.IOException;
import java.io.InputStream;
diff --git a/sentry-core/src/main/java/io/sentry/core/IUnknownPropertiesConsumer.java b/sentry/src/main/java/io/sentry/IUnknownPropertiesConsumer.java
similarity index 89%
rename from sentry-core/src/main/java/io/sentry/core/IUnknownPropertiesConsumer.java
rename to sentry/src/main/java/io/sentry/IUnknownPropertiesConsumer.java
index f6e31a6df..5da2df302 100644
--- a/sentry-core/src/main/java/io/sentry/core/IUnknownPropertiesConsumer.java
+++ b/sentry/src/main/java/io/sentry/IUnknownPropertiesConsumer.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/Integration.java b/sentry/src/main/java/io/sentry/Integration.java
similarity index 93%
rename from sentry-core/src/main/java/io/sentry/core/Integration.java
rename to sentry/src/main/java/io/sentry/Integration.java
index 1f80bdd20..346528671 100644
--- a/sentry-core/src/main/java/io/sentry/core/Integration.java
+++ b/sentry/src/main/java/io/sentry/Integration.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
/**
* Code that provides middlewares, bindings or hooks into certain frameworks or environments, along
diff --git a/sentry-core/src/main/java/io/sentry/core/InvalidDsnException.java b/sentry/src/main/java/io/sentry/InvalidDsnException.java
similarity index 96%
rename from sentry-core/src/main/java/io/sentry/core/InvalidDsnException.java
rename to sentry/src/main/java/io/sentry/InvalidDsnException.java
index 7c4e4966f..8ff38297a 100644
--- a/sentry-core/src/main/java/io/sentry/core/InvalidDsnException.java
+++ b/sentry/src/main/java/io/sentry/InvalidDsnException.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
public final class InvalidDsnException extends RuntimeException {
private static final long serialVersionUID = 412945154259913013L;
diff --git a/sentry-core/src/main/java/io/sentry/core/MainEventProcessor.java b/sentry/src/main/java/io/sentry/MainEventProcessor.java
similarity index 92%
rename from sentry-core/src/main/java/io/sentry/core/MainEventProcessor.java
rename to sentry/src/main/java/io/sentry/MainEventProcessor.java
index 6d2bc6597..a1a822698 100644
--- a/sentry-core/src/main/java/io/sentry/core/MainEventProcessor.java
+++ b/sentry/src/main/java/io/sentry/MainEventProcessor.java
@@ -1,11 +1,12 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.protocol.SentryException;
-import io.sentry.core.util.ApplyScopeUtils;
-import io.sentry.core.util.Objects;
+import io.sentry.protocol.SentryException;
+import io.sentry.util.ApplyScopeUtils;
+import io.sentry.util.Objects;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ApiStatus.Internal
@@ -37,7 +38,7 @@ public final class MainEventProcessor implements EventProcessor {
}
@Override
- public SentryEvent process(SentryEvent event, @Nullable Object hint) {
+ public @NotNull SentryEvent process(SentryEvent event, @Nullable Object hint) {
if (event.getPlatform() == null) {
// this actually means JVM related.
event.setPlatform("java");
diff --git a/sentry-core/src/main/java/io/sentry/core/NoOpEnvelopeReader.java b/sentry/src/main/java/io/sentry/NoOpEnvelopeReader.java
similarity index 95%
rename from sentry-core/src/main/java/io/sentry/core/NoOpEnvelopeReader.java
rename to sentry/src/main/java/io/sentry/NoOpEnvelopeReader.java
index 208cbc438..f06533c32 100644
--- a/sentry-core/src/main/java/io/sentry/core/NoOpEnvelopeReader.java
+++ b/sentry/src/main/java/io/sentry/NoOpEnvelopeReader.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import java.io.IOException;
import java.io.InputStream;
diff --git a/sentry-core/src/main/java/io/sentry/core/NoOpHub.java b/sentry/src/main/java/io/sentry/NoOpHub.java
similarity index 95%
rename from sentry-core/src/main/java/io/sentry/core/NoOpHub.java
rename to sentry/src/main/java/io/sentry/NoOpHub.java
index fb2b14bb0..8319ff554 100644
--- a/sentry-core/src/main/java/io/sentry/core/NoOpHub.java
+++ b/sentry/src/main/java/io/sentry/NoOpHub.java
@@ -1,7 +1,7 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.protocol.SentryId;
-import io.sentry.core.protocol.User;
+import io.sentry.protocol.SentryId;
+import io.sentry.protocol.User;
import java.util.List;
import org.jetbrains.annotations.Nullable;
diff --git a/sentry-core/src/main/java/io/sentry/core/NoOpLogger.java b/sentry/src/main/java/io/sentry/NoOpLogger.java
similarity index 95%
rename from sentry-core/src/main/java/io/sentry/core/NoOpLogger.java
rename to sentry/src/main/java/io/sentry/NoOpLogger.java
index d7aaf3a8c..32fc35a85 100644
--- a/sentry-core/src/main/java/io/sentry/core/NoOpLogger.java
+++ b/sentry/src/main/java/io/sentry/NoOpLogger.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
/** No-op implementation of ILogger */
public final class NoOpLogger implements ILogger {
diff --git a/sentry-core/src/main/java/io/sentry/core/NoOpSentryClient.java b/sentry/src/main/java/io/sentry/NoOpSentryClient.java
similarity index 92%
rename from sentry-core/src/main/java/io/sentry/core/NoOpSentryClient.java
rename to sentry/src/main/java/io/sentry/NoOpSentryClient.java
index 69e7f3bb4..65c6256d7 100644
--- a/sentry-core/src/main/java/io/sentry/core/NoOpSentryClient.java
+++ b/sentry/src/main/java/io/sentry/NoOpSentryClient.java
@@ -1,6 +1,6 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.protocol.SentryId;
+import io.sentry.protocol.SentryId;
import org.jetbrains.annotations.Nullable;
final class NoOpSentryClient implements ISentryClient {
diff --git a/sentry-core/src/main/java/io/sentry/core/NoOpSerializer.java b/sentry/src/main/java/io/sentry/NoOpSerializer.java
similarity index 97%
rename from sentry-core/src/main/java/io/sentry/core/NoOpSerializer.java
rename to sentry/src/main/java/io/sentry/NoOpSerializer.java
index ab988299f..f4047d47c 100644
--- a/sentry-core/src/main/java/io/sentry/core/NoOpSerializer.java
+++ b/sentry/src/main/java/io/sentry/NoOpSerializer.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import java.io.IOException;
import java.io.InputStream;
diff --git a/sentry-core/src/main/java/io/sentry/core/OptionsContainer.java b/sentry/src/main/java/io/sentry/OptionsContainer.java
similarity index 96%
rename from sentry-core/src/main/java/io/sentry/core/OptionsContainer.java
rename to sentry/src/main/java/io/sentry/OptionsContainer.java
index d5c6835f8..a3593c537 100644
--- a/sentry-core/src/main/java/io/sentry/core/OptionsContainer.java
+++ b/sentry/src/main/java/io/sentry/OptionsContainer.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import java.lang.reflect.InvocationTargetException;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/EnvelopeSender.java b/sentry/src/main/java/io/sentry/OutboxSender.java
similarity index 70%
rename from sentry-core/src/main/java/io/sentry/core/EnvelopeSender.java
rename to sentry/src/main/java/io/sentry/OutboxSender.java
index 676301e95..55c5b2104 100644
--- a/sentry-core/src/main/java/io/sentry/core/EnvelopeSender.java
+++ b/sentry/src/main/java/io/sentry/OutboxSender.java
@@ -1,14 +1,14 @@
-package io.sentry.core;
+package io.sentry;
-import static io.sentry.core.SentryLevel.ERROR;
-import static io.sentry.core.cache.SessionCache.PREFIX_CURRENT_SESSION_FILE;
+import static io.sentry.SentryLevel.ERROR;
+import static io.sentry.cache.EnvelopeCache.PREFIX_CURRENT_SESSION_FILE;
-import io.sentry.core.hints.Flushable;
-import io.sentry.core.hints.Retryable;
-import io.sentry.core.hints.SubmissionResult;
-import io.sentry.core.util.CollectionUtils;
-import io.sentry.core.util.LogUtils;
-import io.sentry.core.util.Objects;
+import io.sentry.hints.Flushable;
+import io.sentry.hints.Retryable;
+import io.sentry.hints.SubmissionResult;
+import io.sentry.util.CollectionUtils;
+import io.sentry.util.LogUtils;
+import io.sentry.util.Objects;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
@@ -24,7 +24,7 @@
import org.jetbrains.annotations.Nullable;
@ApiStatus.Internal
-public final class EnvelopeSender extends DirectoryProcessor implements IEnvelopeSender {
+public final class OutboxSender extends DirectoryProcessor implements IEnvelopeSender {
@SuppressWarnings("CharsetObjectCanBeUsed")
private static final Charset UTF_8 = Charset.forName("UTF-8");
@@ -34,7 +34,7 @@ public final class EnvelopeSender extends DirectoryProcessor implements IEnvelop
private final @NotNull ISerializer serializer;
private final @NotNull ILogger logger;
- public EnvelopeSender(
+ public OutboxSender(
final @NotNull IHub hub,
final @NotNull IEnvelopeReader envelopeReader,
final @NotNull ISerializer serializer,
@@ -145,55 +145,8 @@ private void processEnvelope(final @NotNull SentryEnvelope envelope, final @Null
"Timed out waiting for event submission: %s",
event.getEventId());
- // TODO: find out about the time out
- // if (hint instanceof Retryable) {
- // ((Retryable) hint).setRetry(true);
- // }
-
- break;
- }
- } else {
- LogUtils.logIfNotFlushable(logger, hint);
- }
- }
- } catch (Exception e) {
- logger.log(ERROR, "Item failed to process.", e);
- }
- } else if (SentryItemType.Session.equals(item.getHeader().getType())) {
- try (final Reader reader =
- new BufferedReader(
- new InputStreamReader(new ByteArrayInputStream(item.getData()), UTF_8))) {
- final Session session = serializer.deserializeSession(reader);
- if (session == null) {
- logger.log(
- SentryLevel.ERROR,
- "Item %d of type %s returned null by the parser.",
- items,
- item.getHeader().getType());
- } else {
- // TODO: Bundle all session in a single envelope
- hub.captureEnvelope(
- SentryEnvelope.fromSession(
- serializer, session, envelope.getHeader().getSdkVersion()),
- hint);
- logger.log(SentryLevel.DEBUG, "Item %d is being captured.", items);
-
- if (hint instanceof Flushable) {
- logger.log(SentryLevel.DEBUG, "Going to wait flush %d item.", items);
- if (!((Flushable) hint).waitFlush()) {
- logger.log(
- SentryLevel.WARNING,
- "Timed out waiting for item submission: %s",
- session.getSessionId());
-
- // TODO: find out about the time out
- // if (hint instanceof Retryable) {
- // ((Retryable) hint).setRetry(true);
- // }
-
break;
}
- logger.log(SentryLevel.DEBUG, "Flushed %d item.", items);
} else {
LogUtils.logIfNotFlushable(logger, hint);
}
diff --git a/sentry-core/src/main/java/io/sentry/core/Scope.java b/sentry/src/main/java/io/sentry/Scope.java
similarity index 96%
rename from sentry-core/src/main/java/io/sentry/core/Scope.java
rename to sentry/src/main/java/io/sentry/Scope.java
index 8860de7f1..4bea21754 100644
--- a/sentry-core/src/main/java/io/sentry/core/Scope.java
+++ b/sentry/src/main/java/io/sentry/Scope.java
@@ -1,7 +1,7 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.protocol.Contexts;
-import io.sentry.core.protocol.User;
+import io.sentry.protocol.Contexts;
+import io.sentry.protocol.User;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -392,11 +392,19 @@ public void addEventProcessor(@NotNull EventProcessor eventProcessor) {
* Callback to do atomic operations on session
*
* @param sessionCallback the IWithSession callback
+ * @return a clone of the Session after executing the callback and mutating the session
*/
- void withSession(@NotNull IWithSession sessionCallback) {
+ @Nullable
+ Session withSession(@NotNull IWithSession sessionCallback) {
+ Session cloneSession = null;
synchronized (sessionLock) {
sessionCallback.accept(session);
+
+ if (session != null) {
+ cloneSession = session.clone();
+ }
}
+ return cloneSession;
}
/** the IWithSession callback */
diff --git a/sentry-core/src/main/java/io/sentry/core/ScopeCallback.java b/sentry/src/main/java/io/sentry/ScopeCallback.java
similarity index 71%
rename from sentry-core/src/main/java/io/sentry/core/ScopeCallback.java
rename to sentry/src/main/java/io/sentry/ScopeCallback.java
index 90d2e51f2..a20632c46 100644
--- a/sentry-core/src/main/java/io/sentry/core/ScopeCallback.java
+++ b/sentry/src/main/java/io/sentry/ScopeCallback.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
public interface ScopeCallback {
void run(Scope scope);
diff --git a/sentry-core/src/main/java/io/sentry/core/SendCachedEventFireAndForgetIntegration.java b/sentry/src/main/java/io/sentry/SendCachedEnvelopeFireAndForgetIntegration.java
similarity index 91%
rename from sentry-core/src/main/java/io/sentry/core/SendCachedEventFireAndForgetIntegration.java
rename to sentry/src/main/java/io/sentry/SendCachedEnvelopeFireAndForgetIntegration.java
index 9dabe1bad..9ecef1a77 100644
--- a/sentry-core/src/main/java/io/sentry/core/SendCachedEventFireAndForgetIntegration.java
+++ b/sentry/src/main/java/io/sentry/SendCachedEnvelopeFireAndForgetIntegration.java
@@ -1,12 +1,12 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.util.Objects;
+import io.sentry.util.Objects;
import java.io.File;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/** Sends cached events over when your App. is starting. */
-public final class SendCachedEventFireAndForgetIntegration implements Integration {
+public final class SendCachedEnvelopeFireAndForgetIntegration implements Integration {
private final SendFireAndForgetFactory factory;
@@ -46,7 +46,8 @@ default boolean hasValidPath(final @Nullable String dirPath, final @NotNull ILog
}
}
- public SendCachedEventFireAndForgetIntegration(final @NotNull SendFireAndForgetFactory factory) {
+ public SendCachedEnvelopeFireAndForgetIntegration(
+ final @NotNull SendFireAndForgetFactory factory) {
this.factory = Objects.requireNonNull(factory, "SendFireAndForgetFactory is required");
}
diff --git a/sentry-core/src/main/java/io/sentry/core/SendFireAndForgetEnvelopeSender.java b/sentry/src/main/java/io/sentry/SendFireAndForgetEnvelopeSender.java
similarity index 64%
rename from sentry-core/src/main/java/io/sentry/core/SendFireAndForgetEnvelopeSender.java
rename to sentry/src/main/java/io/sentry/SendFireAndForgetEnvelopeSender.java
index 2f05ad305..1e90cabc1 100644
--- a/sentry-core/src/main/java/io/sentry/core/SendFireAndForgetEnvelopeSender.java
+++ b/sentry/src/main/java/io/sentry/SendFireAndForgetEnvelopeSender.java
@@ -1,26 +1,26 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.util.Objects;
+import io.sentry.util.Objects;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ApiStatus.Internal
public final class SendFireAndForgetEnvelopeSender
- implements SendCachedEventFireAndForgetIntegration.SendFireAndForgetFactory {
+ implements SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory {
- private final @NotNull SendCachedEventFireAndForgetIntegration.SendFireAndForgetDirPath
+ private final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetDirPath
sendFireAndForgetDirPath;
public SendFireAndForgetEnvelopeSender(
- final @NotNull SendCachedEventFireAndForgetIntegration.SendFireAndForgetDirPath
+ final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetDirPath
sendFireAndForgetDirPath) {
this.sendFireAndForgetDirPath =
Objects.requireNonNull(sendFireAndForgetDirPath, "SendFireAndForgetDirPath is required");
}
@Override
- public @Nullable SendCachedEventFireAndForgetIntegration.SendFireAndForget create(
+ public @Nullable SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForget create(
final @NotNull IHub hub, final @NotNull SentryOptions options) {
Objects.requireNonNull(hub, "Hub is required");
Objects.requireNonNull(options, "SentryOptions is required");
@@ -33,11 +33,7 @@ public SendFireAndForgetEnvelopeSender(
final EnvelopeSender envelopeSender =
new EnvelopeSender(
- hub,
- options.getEnvelopeReader(),
- options.getSerializer(),
- options.getLogger(),
- options.getFlushTimeoutMillis());
+ hub, options.getSerializer(), options.getLogger(), options.getFlushTimeoutMillis());
return processDir(envelopeSender, dirPath, options.getLogger());
}
diff --git a/sentry/src/main/java/io/sentry/SendFireAndForgetOutboxSender.java b/sentry/src/main/java/io/sentry/SendFireAndForgetOutboxSender.java
new file mode 100644
index 000000000..766b9fa4e
--- /dev/null
+++ b/sentry/src/main/java/io/sentry/SendFireAndForgetOutboxSender.java
@@ -0,0 +1,44 @@
+package io.sentry;
+
+import io.sentry.util.Objects;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+@ApiStatus.Internal
+public final class SendFireAndForgetOutboxSender
+ implements SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory {
+
+ private final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetDirPath
+ sendFireAndForgetDirPath;
+
+ public SendFireAndForgetOutboxSender(
+ final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetDirPath
+ sendFireAndForgetDirPath) {
+ this.sendFireAndForgetDirPath =
+ Objects.requireNonNull(sendFireAndForgetDirPath, "SendFireAndForgetDirPath is required");
+ }
+
+ @Override
+ public @Nullable SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForget create(
+ final @NotNull IHub hub, final @NotNull SentryOptions options) {
+ Objects.requireNonNull(hub, "Hub is required");
+ Objects.requireNonNull(options, "SentryOptions is required");
+
+ final String dirPath = sendFireAndForgetDirPath.getDirPath();
+ if (!hasValidPath(dirPath, options.getLogger())) {
+ options.getLogger().log(SentryLevel.ERROR, "No outbox dir path is defined in options.");
+ return null;
+ }
+
+ final OutboxSender outboxSender =
+ new OutboxSender(
+ hub,
+ options.getEnvelopeReader(),
+ options.getSerializer(),
+ options.getLogger(),
+ options.getFlushTimeoutMillis());
+
+ return processDir(outboxSender, dirPath, options.getLogger());
+ }
+}
diff --git a/sentry-core/src/main/java/io/sentry/core/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java
similarity index 96%
rename from sentry-core/src/main/java/io/sentry/core/Sentry.java
rename to sentry/src/main/java/io/sentry/Sentry.java
index 90adbb4c0..36c8a684f 100644
--- a/sentry-core/src/main/java/io/sentry/core/Sentry.java
+++ b/sentry/src/main/java/io/sentry/Sentry.java
@@ -1,12 +1,12 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.cache.DiskCache;
-import io.sentry.core.cache.SessionCache;
-import io.sentry.core.protocol.SentryId;
-import io.sentry.core.protocol.User;
+import io.sentry.cache.EnvelopeCache;
+import io.sentry.protocol.SentryId;
+import io.sentry.protocol.User;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
+import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -123,6 +123,16 @@ public static void init(
init(options, globalHubMode);
}
+ /**
+ * Initializes the SDK with a SentryOptions.
+ *
+ * @param options options the SentryOptions
+ */
+ @ApiStatus.Internal
+ public static void init(final @NotNull SentryOptions options) {
+ init(options, GLOBAL_HUB_DEFAULT_MODE);
+ }
+
/**
* Initializes the SDK with a SentryOptions and globalHubMode
*
@@ -197,11 +207,7 @@ private static boolean initConfigurations(final @NotNull SentryOptions options)
final File outboxDir = new File(options.getOutboxPath());
outboxDir.mkdirs();
- final File sessionsDir = new File(options.getSessionsPath());
- sessionsDir.mkdirs();
-
- options.setEventDiskCache(new DiskCache(options));
- options.setEnvelopeDiskCache(new SessionCache(options));
+ options.setEnvelopeDiskCache(new EnvelopeCache(options));
} else {
logger.log(SentryLevel.INFO, "No outbox dir path is defined in options.");
}
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java
similarity index 62%
rename from sentry-core/src/main/java/io/sentry/core/SentryClient.java
rename to sentry/src/main/java/io/sentry/SentryClient.java
index e65aac25b..2dee70f8f 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryClient.java
+++ b/sentry/src/main/java/io/sentry/SentryClient.java
@@ -1,17 +1,16 @@
-package io.sentry.core;
-
-import io.sentry.core.hints.DiskFlushNotification;
-import io.sentry.core.hints.SessionEndHint;
-import io.sentry.core.hints.SessionUpdateHint;
-import io.sentry.core.protocol.SentryId;
-import io.sentry.core.transport.Connection;
-import io.sentry.core.transport.ITransport;
-import io.sentry.core.transport.NoOpTransport;
-import io.sentry.core.util.ApplyScopeUtils;
-import io.sentry.core.util.Objects;
+package io.sentry;
+
+import io.sentry.hints.DiskFlushNotification;
+import io.sentry.protocol.SentryId;
+import io.sentry.transport.Connection;
+import io.sentry.transport.ITransport;
+import io.sentry.transport.NoOpTransport;
+import io.sentry.util.ApplyScopeUtils;
+import io.sentry.util.Objects;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Random;
import org.jetbrains.annotations.ApiStatus;
@@ -48,9 +47,7 @@ public SentryClient(final @NotNull SentryOptions options, @Nullable Connection c
}
if (connection == null) {
- connection =
- AsyncConnectionFactory.create(
- options, options.getEventDiskCache(), options.getEnvelopeDiskCache());
+ connection = AsyncConnectionFactory.create(options, options.getEnvelopeDiskCache());
}
this.connection = connection;
random = options.getSampleRate() == null ? null : new Random();
@@ -70,7 +67,7 @@ public SentryClient(final @NotNull SentryOptions options, @Nullable Connection c
event = applyScope(event, scope, hint);
if (event == null) {
- return SentryId.EMPTY_ID;
+ options.getLogger().log(SentryLevel.DEBUG, "Event was dropped by applyScope");
}
} else {
options
@@ -78,53 +75,112 @@ public SentryClient(final @NotNull SentryOptions options, @Nullable Connection c
.log(SentryLevel.DEBUG, "Event was cached so not applying scope: %s", event.getEventId());
}
- for (EventProcessor processor : options.getEventProcessors()) {
- event = processor.process(event, hint);
+ event = processEvent(event, hint, options.getEventProcessors());
- if (event == null) {
+ Session session = null;
+
+ if (event != null) {
+ session = updateSessionData(event, hint, scope);
+
+ if (!sample()) {
options
.getLogger()
.log(
SentryLevel.DEBUG,
- "Event was dropped by processor: %s",
- processor.getClass().getName());
- break;
+ "Event %s was dropped due to sampling decision.",
+ event.getEventId());
+ // setting event as null to not be sent as its been discarded by sample rate
+ event = null;
}
}
- if (event == null) {
- return SentryId.EMPTY_ID;
- }
-
- // TODO: should it be done on the HUB?
- updateSessionData(event, hint, scope);
+ if (event != null) {
+ event = executeBeforeSend(event, hint);
- if (!sample()) {
- options
- .getLogger()
- .log(
- SentryLevel.DEBUG,
- "Event %s was dropped due to sampling decision.",
- event.getEventId());
- return SentryId.EMPTY_ID;
+ if (event == null) {
+ options.getLogger().log(SentryLevel.DEBUG, "Event was dropped by beforeSend");
+ }
}
- event = executeBeforeSend(event, hint);
+ SentryId sentryId = SentryId.EMPTY_ID;
- if (event == null) {
- options.getLogger().log(SentryLevel.DEBUG, "Event was dropped by beforeSend");
- return SentryId.EMPTY_ID;
+ if (event != null) {
+ sentryId = event.getEventId();
}
try {
- connection.send(event, hint);
+ final SentryEnvelope envelope = buildEnvelope(event, session);
+
+ if (envelope != null) {
+ connection.send(envelope, hint);
+ }
} catch (IOException e) {
- options
- .getLogger()
- .log(SentryLevel.WARNING, "Capturing event " + event.getEventId() + " failed.", e);
+ options.getLogger().log(SentryLevel.WARNING, e, "Capturing event %s failed.", sentryId);
+
+ // if there was an error capturing the event, we return an emptyId
+ sentryId = SentryId.EMPTY_ID;
}
- return event.getEventId();
+ return sentryId;
+ }
+
+ private @Nullable SentryEnvelope buildEnvelope(
+ final @Nullable SentryEvent event, final @Nullable Session session) throws IOException {
+ SentryId sentryId = null;
+
+ final List envelopeItems = new ArrayList<>();
+
+ if (event != null) {
+ final SentryEnvelopeItem eventItem =
+ SentryEnvelopeItem.fromEvent(options.getSerializer(), event);
+ envelopeItems.add(eventItem);
+ sentryId = event.getEventId();
+ }
+
+ if (session != null) {
+ final SentryEnvelopeItem sessionItem =
+ SentryEnvelopeItem.fromSession(options.getSerializer(), session);
+ envelopeItems.add(sessionItem);
+ }
+
+ if (!envelopeItems.isEmpty()) {
+ final SentryEnvelopeHeader envelopeHeader =
+ new SentryEnvelopeHeader(sentryId, options.getSdkVersion());
+ return new SentryEnvelope(envelopeHeader, envelopeItems);
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private SentryEvent processEvent(
+ @NotNull SentryEvent event,
+ final @Nullable Object hint,
+ final @NotNull List eventProcessors) {
+ for (EventProcessor processor : eventProcessors) {
+ try {
+ event = processor.process(event, hint);
+ } catch (Exception e) {
+ options
+ .getLogger()
+ .log(
+ SentryLevel.ERROR,
+ e,
+ "An exception occurred while processing event by processor: %s",
+ processor.getClass().getName());
+ }
+
+ if (event == null) {
+ options
+ .getLogger()
+ .log(
+ SentryLevel.DEBUG,
+ "Event was dropped by a processor: %s",
+ processor.getClass().getName());
+ break;
+ }
+ }
+ return event;
}
/**
@@ -135,53 +191,52 @@ public SentryClient(final @NotNull SentryOptions options, @Nullable Connection c
* @param scope the Scope or null
*/
@TestOnly
- void updateSessionData(
+ @Nullable
+ Session updateSessionData(
final @NotNull SentryEvent event, final @Nullable Object hint, final @Nullable Scope scope) {
+ Session clonedSession = null;
+
if (ApplyScopeUtils.shouldApplyScopeData(hint)) {
if (scope != null) {
- scope.withSession(
- session -> {
- if (session != null) {
- Session.State status = null;
- if (event.isCrashed()) {
- status = Session.State.Crashed;
- }
-
- boolean crashedOrErrored = false;
- if (Session.State.Crashed == status || event.isErrored()) {
- crashedOrErrored = true;
- }
-
- String userAgent = null;
- if (event.getRequest() != null && event.getRequest().getHeaders() != null) {
- if (event.getRequest().getHeaders().containsKey("user-agent")) {
- userAgent = event.getRequest().getHeaders().get("user-agent");
- }
- }
-
- if (session.update(status, userAgent, crashedOrErrored)) {
-
- Object sessionHint;
-
- // if hint is DiskFlushNotification, it means we have an uncaughtException
- // and we can end the session.
- if (hint instanceof DiskFlushNotification) {
- sessionHint = new SessionEndHint();
- session.end();
+ clonedSession =
+ scope.withSession(
+ session -> {
+ if (session != null) {
+ Session.State status = null;
+ if (event.isCrashed()) {
+ status = Session.State.Crashed;
+ }
+
+ boolean crashedOrErrored = false;
+ if (Session.State.Crashed == status || event.isErrored()) {
+ crashedOrErrored = true;
+ }
+
+ String userAgent = null;
+ if (event.getRequest() != null && event.getRequest().getHeaders() != null) {
+ if (event.getRequest().getHeaders().containsKey("user-agent")) {
+ userAgent = event.getRequest().getHeaders().get("user-agent");
+ }
+ }
+
+ if (session.update(status, userAgent, crashedOrErrored)) {
+ // if hint is DiskFlushNotification, it means we have an uncaughtException
+ // and we can end the session.
+ if (hint instanceof DiskFlushNotification) {
+ session.end();
+ }
+ }
} else {
- // otherwise we just cache in the disk but do not flush to the network.
- sessionHint = new SessionUpdateHint();
+ options
+ .getLogger()
+ .log(SentryLevel.INFO, "Session is null on scope.withSession");
}
- captureSession(session, sessionHint);
- }
- } else {
- options.getLogger().log(SentryLevel.INFO, "Session is null on scope.withSession");
- }
- });
+ });
} else {
options.getLogger().log(SentryLevel.INFO, "Scope is null on client.captureEvent");
}
}
+ return clonedSession;
}
@ApiStatus.Internal
@@ -274,19 +329,7 @@ public void captureSession(final @NotNull Session session, final @Nullable Objec
event.setLevel(scope.getLevel());
}
- for (EventProcessor processor : scope.getEventProcessors()) {
- event = processor.process(event, hint);
-
- if (event == null) {
- options
- .getLogger()
- .log(
- SentryLevel.DEBUG,
- "Event was dropped by scope processor: %s",
- processor.getClass().getName());
- break;
- }
- }
+ event = processEvent(event, hint, scope.getEventProcessors());
}
return event;
}
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryEnvelope.java b/sentry/src/main/java/io/sentry/SentryEnvelope.java
similarity index 78%
rename from sentry-core/src/main/java/io/sentry/core/SentryEnvelope.java
rename to sentry/src/main/java/io/sentry/SentryEnvelope.java
index 006c83a56..f2a967b72 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryEnvelope.java
+++ b/sentry/src/main/java/io/sentry/SentryEnvelope.java
@@ -1,8 +1,8 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.protocol.SdkVersion;
-import io.sentry.core.protocol.SentryId;
-import io.sentry.core.util.Objects;
+import io.sentry.protocol.SdkVersion;
+import io.sentry.protocol.SentryId;
+import io.sentry.util.Objects;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -64,4 +64,16 @@ public SentryEnvelope(
return new SentryEnvelope(
null, sdkVersion, SentryEnvelopeItem.fromSession(serializer, session));
}
+
+ public static @NotNull SentryEnvelope fromEvent(
+ final @NotNull ISerializer serializer,
+ final @NotNull SentryEvent event,
+ final @Nullable SdkVersion sdkVersion)
+ throws IOException {
+ Objects.requireNonNull(serializer, "Serializer is required.");
+ Objects.requireNonNull(event, "Event is required.");
+
+ return new SentryEnvelope(
+ event.getEventId(), sdkVersion, SentryEnvelopeItem.fromEvent(serializer, event));
+ }
}
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryEnvelopeHeader.java b/sentry/src/main/java/io/sentry/SentryEnvelopeHeader.java
similarity index 88%
rename from sentry-core/src/main/java/io/sentry/core/SentryEnvelopeHeader.java
rename to sentry/src/main/java/io/sentry/SentryEnvelopeHeader.java
index 03a6e14a1..6c8dc80bb 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryEnvelopeHeader.java
+++ b/sentry/src/main/java/io/sentry/SentryEnvelopeHeader.java
@@ -1,7 +1,7 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.protocol.SdkVersion;
-import io.sentry.core.protocol.SentryId;
+import io.sentry.protocol.SdkVersion;
+import io.sentry.protocol.SentryId;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryEnvelopeHeaderAdapter.java b/sentry/src/main/java/io/sentry/SentryEnvelopeHeaderAdapter.java
similarity index 97%
rename from sentry-core/src/main/java/io/sentry/core/SentryEnvelopeHeaderAdapter.java
rename to sentry/src/main/java/io/sentry/SentryEnvelopeHeaderAdapter.java
index 386098bb7..e0266bb8e 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryEnvelopeHeaderAdapter.java
+++ b/sentry/src/main/java/io/sentry/SentryEnvelopeHeaderAdapter.java
@@ -1,12 +1,12 @@
-package io.sentry.core;
+package io.sentry;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
-import io.sentry.core.protocol.SdkVersion;
-import io.sentry.core.protocol.SentryId;
-import io.sentry.core.protocol.SentryPackage;
+import io.sentry.protocol.SdkVersion;
+import io.sentry.protocol.SentryId;
+import io.sentry.protocol.SentryPackage;
import java.io.IOException;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryEnvelopeItem.java b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java
similarity index 87%
rename from sentry-core/src/main/java/io/sentry/core/SentryEnvelopeItem.java
rename to sentry/src/main/java/io/sentry/SentryEnvelopeItem.java
index 74b5d2ea6..7e71d6552 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryEnvelopeItem.java
+++ b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java
@@ -1,10 +1,14 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.util.Objects;
+import io.sentry.util.Objects;
+import java.io.BufferedReader;
import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
+import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.concurrent.Callable;
@@ -72,6 +76,16 @@ public final class SentryEnvelopeItem {
return new SentryEnvelopeItem(itemHeader, () -> cachedItem.getBytes());
}
+ public @Nullable SentryEvent getEvent(final @NotNull ISerializer serializer) throws Exception {
+ if (header == null || header.getType() != SentryItemType.Event) {
+ return null;
+ }
+ try (final Reader eventReader =
+ new BufferedReader(new InputStreamReader(new ByteArrayInputStream(getData()), UTF_8))) {
+ return serializer.deserializeEvent(eventReader);
+ }
+ }
+
public static @NotNull SentryEnvelopeItem fromEvent(
final @NotNull ISerializer serializer, final @NotNull SentryEvent event) throws IOException {
Objects.requireNonNull(serializer, "ISerializer is required.");
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryEnvelopeItemHeader.java b/sentry/src/main/java/io/sentry/SentryEnvelopeItemHeader.java
similarity index 96%
rename from sentry-core/src/main/java/io/sentry/core/SentryEnvelopeItemHeader.java
rename to sentry/src/main/java/io/sentry/SentryEnvelopeItemHeader.java
index caa2bea77..2e39b3520 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryEnvelopeItemHeader.java
+++ b/sentry/src/main/java/io/sentry/SentryEnvelopeItemHeader.java
@@ -1,6 +1,6 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.util.Objects;
+import io.sentry.util.Objects;
import java.util.concurrent.Callable;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryEnvelopeItemHeaderAdapter.java b/sentry/src/main/java/io/sentry/SentryEnvelopeItemHeaderAdapter.java
similarity index 97%
rename from sentry-core/src/main/java/io/sentry/core/SentryEnvelopeItemHeaderAdapter.java
rename to sentry/src/main/java/io/sentry/SentryEnvelopeItemHeaderAdapter.java
index 8a161e54d..ee3f9030d 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryEnvelopeItemHeaderAdapter.java
+++ b/sentry/src/main/java/io/sentry/SentryEnvelopeItemHeaderAdapter.java
@@ -1,10 +1,10 @@
-package io.sentry.core;
+package io.sentry;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
-import io.sentry.core.util.StringUtils;
+import io.sentry.util.StringUtils;
import java.io.IOException;
import java.util.Locale;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryEvent.java b/sentry/src/main/java/io/sentry/SentryEvent.java
similarity index 97%
rename from sentry-core/src/main/java/io/sentry/core/SentryEvent.java
rename to sentry/src/main/java/io/sentry/SentryEvent.java
index f6a1a8d41..1bd1b1a44 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryEvent.java
+++ b/sentry/src/main/java/io/sentry/SentryEvent.java
@@ -1,6 +1,6 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.protocol.*;
+import io.sentry.protocol.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@@ -67,7 +67,8 @@ public Date getTimestamp() {
}
@Nullable
- Throwable getThrowable() {
+ @ApiStatus.Internal
+ public Throwable getThrowable() {
return throwable;
}
@@ -334,14 +335,11 @@ public void setDebugMeta(DebugMeta debugMeta) {
}
/**
- * Returns true if Level is Fatal or any exception was unhandled by the user.
+ * Returns true if any exception was unhandled by the user.
*
* @return true if its crashed or false otherwise
*/
public boolean isCrashed() {
- if (level == SentryLevel.FATAL) {
- return true;
- }
if (exception != null) {
for (SentryException e : exception.getValues()) {
if (e.getMechanism() != null
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryExceptionFactory.java b/sentry/src/main/java/io/sentry/SentryExceptionFactory.java
similarity index 94%
rename from sentry-core/src/main/java/io/sentry/core/SentryExceptionFactory.java
rename to sentry/src/main/java/io/sentry/SentryExceptionFactory.java
index ffc5db043..df6d6cf34 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryExceptionFactory.java
+++ b/sentry/src/main/java/io/sentry/SentryExceptionFactory.java
@@ -1,10 +1,10 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.exception.ExceptionMechanismException;
-import io.sentry.core.protocol.Mechanism;
-import io.sentry.core.protocol.SentryException;
-import io.sentry.core.protocol.SentryStackTrace;
-import io.sentry.core.util.Objects;
+import io.sentry.exception.ExceptionMechanismException;
+import io.sentry.protocol.Mechanism;
+import io.sentry.protocol.SentryException;
+import io.sentry.protocol.SentryStackTrace;
+import io.sentry.util.Objects;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryExecutorService.java b/sentry/src/main/java/io/sentry/SentryExecutorService.java
similarity index 98%
rename from sentry-core/src/main/java/io/sentry/core/SentryExecutorService.java
rename to sentry/src/main/java/io/sentry/SentryExecutorService.java
index 9df4773d5..d115988d8 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryExecutorService.java
+++ b/sentry/src/main/java/io/sentry/SentryExecutorService.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryItemType.java b/sentry/src/main/java/io/sentry/SentryItemType.java
similarity index 94%
rename from sentry-core/src/main/java/io/sentry/core/SentryItemType.java
rename to sentry/src/main/java/io/sentry/SentryItemType.java
index 145bdc264..3ad9b3afb 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryItemType.java
+++ b/sentry/src/main/java/io/sentry/SentryItemType.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryLevel.java b/sentry/src/main/java/io/sentry/SentryLevel.java
similarity index 87%
rename from sentry-core/src/main/java/io/sentry/core/SentryLevel.java
rename to sentry/src/main/java/io/sentry/SentryLevel.java
index 244f2331c..e1a6c2dbf 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryLevel.java
+++ b/sentry/src/main/java/io/sentry/SentryLevel.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
/** the SentryLevel */
public enum SentryLevel {
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java
similarity index 92%
rename from sentry-core/src/main/java/io/sentry/core/SentryOptions.java
rename to sentry/src/main/java/io/sentry/SentryOptions.java
index c3246f8d3..5569311fa 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryOptions.java
+++ b/sentry/src/main/java/io/sentry/SentryOptions.java
@@ -1,15 +1,13 @@
-package io.sentry.core;
+package io.sentry;
import com.jakewharton.nopen.annotation.Open;
-import io.sentry.core.cache.IEnvelopeCache;
-import io.sentry.core.cache.IEventCache;
-import io.sentry.core.protocol.SdkVersion;
-import io.sentry.core.transport.ITransport;
-import io.sentry.core.transport.ITransportGate;
-import io.sentry.core.transport.NoOpEnvelopeCache;
-import io.sentry.core.transport.NoOpEventCache;
-import io.sentry.core.transport.NoOpTransport;
-import io.sentry.core.transport.NoOpTransportGate;
+import io.sentry.cache.IEnvelopeCache;
+import io.sentry.protocol.SdkVersion;
+import io.sentry.transport.ITransport;
+import io.sentry.transport.ITransportGate;
+import io.sentry.transport.NoOpEnvelopeCache;
+import io.sentry.transport.NoOpTransport;
+import io.sentry.transport.NoOpTransportGate;
import java.io.File;
import java.net.Proxy;
import java.util.List;
@@ -48,7 +46,7 @@ public class SentryOptions {
* background queue and this queue is given a certain amount to drain pending events Default is
* 2000 = 2s
*/
- private long shutdownTimeoutMillis = 2000; // 2s
+ private long shutdownTimeout = 2000; // 2s
/**
* Controls how many seconds to wait before flushing down. Sentry SDKs cache events from a
@@ -101,11 +99,8 @@ public class SentryOptions {
/** The cache dir. size for capping the number of events Default is 10 */
private int cacheDirSize = 10;
- /** The sessions dir. size for capping the number of envelopes Default is 100 */
- private int sessionsDirSize = 100;
-
/** Max. queue size before flushing events/envelopes to the disk */
- private int maxQueueSize = cacheDirSize + sessionsDirSize;
+ private int maxQueueSize = cacheDirSize;
/**
* This variable controls the total amount of breadcrumbs that should be captured Default is 100
@@ -169,8 +164,8 @@ public class SentryOptions {
*/
private boolean attachStacktrace;
- /** Whether to enable automatic session tracking. */
- private boolean enableSessionTracking;
+ /** Whether to enable or disable automatic session tracking. */
+ private boolean enableSessionTracking = true;
/**
* The session tracking interval in millis. This is the interval to end a session if the App goes
@@ -201,15 +196,15 @@ public class SentryOptions {
/** whether to ignore TLS errors */
private boolean bypassSecurity = false;
- /** Reads and caches event json files in the disk */
- private @NotNull IEventCache eventDiskCache = NoOpEventCache.getInstance();
-
/** Reads and caches envelope files in the disk */
private @NotNull IEnvelopeCache envelopeDiskCache = NoOpEnvelopeCache.getInstance();
/** SdkVersion object that contains the Sentry Client Name and its version */
private @Nullable SdkVersion sdkVersion;
+ /** whether to send personal identifiable information along with events */
+ private boolean sendDefaultPii = false;
+
/**
* Adds an event processor
*
@@ -369,7 +364,7 @@ public void setEnableNdk(boolean enableNdk) {
* @return the timeout in Millis
*/
public long getShutdownTimeout() {
- return shutdownTimeoutMillis;
+ return shutdownTimeout;
}
/**
@@ -378,7 +373,7 @@ public long getShutdownTimeout() {
* @param shutdownTimeoutMillis the shutdown timeout in millis
*/
public void setShutdownTimeout(long shutdownTimeoutMillis) {
- this.shutdownTimeoutMillis = shutdownTimeoutMillis;
+ this.shutdownTimeout = shutdownTimeoutMillis;
}
/**
@@ -456,18 +451,6 @@ public void setBeforeBreadcrumb(@Nullable BeforeBreadcrumbCallback beforeBreadcr
return cacheDirPath + File.separator + "outbox";
}
- /**
- * Returns the sessions path if cacheDirPath is set
- *
- * @return the sessions path or null if not set
- */
- public @Nullable String getSessionsPath() {
- if (cacheDirPath == null || cacheDirPath.isEmpty()) {
- return null;
- }
- return cacheDirPath + File.separator + "sessions";
- }
-
/**
* Sets the cache dir. path
*
@@ -753,24 +736,6 @@ public void setServerName(@Nullable String serverName) {
this.serverName = serverName;
}
- /**
- * Returns the sessions dir size
- *
- * @return the dir size
- */
- public int getSessionsDirSize() {
- return sessionsDirSize;
- }
-
- /**
- * Sets the sessions dir size
- *
- * @param sessionsDirSize the sessions dir size
- */
- public void setSessionsDirSize(int sessionsDirSize) {
- this.sessionsDirSize = sessionsDirSize;
- }
-
/**
* Returns the session tracking interval in millis
*
@@ -920,24 +885,6 @@ public void setBypassSecurity(boolean bypassSecurity) {
this.bypassSecurity = bypassSecurity;
}
- /**
- * Returns the EventCache interface
- *
- * @return the EventCache object
- */
- public @NotNull IEventCache getEventDiskCache() {
- return eventDiskCache;
- }
-
- /**
- * Sets the EventCache interface
- *
- * @param eventDiskCache the EventCache object
- */
- public void setEventDiskCache(final @Nullable IEventCache eventDiskCache) {
- this.eventDiskCache = eventDiskCache != null ? eventDiskCache : NoOpEventCache.getInstance();
- }
-
/**
* Returns the EnvelopeCache interface
*
@@ -996,6 +943,14 @@ public void setSdkVersion(final @Nullable SdkVersion sdkVersion) {
this.sdkVersion = sdkVersion;
}
+ public boolean isSendDefaultPii() {
+ return sendDefaultPii;
+ }
+
+ public void setSendDefaultPii(boolean sendDefaultPii) {
+ this.sendDefaultPii = sendDefaultPii;
+ }
+
/** The BeforeSend callback */
public interface BeforeSendCallback {
@@ -1036,6 +991,7 @@ public SentryOptions() {
integrations.add(new ShutdownHookIntegration());
eventProcessors.add(new MainEventProcessor(this));
+ eventProcessors.add(new DuplicateEventDetectionEventProcessor(this));
setSentryClientName(BuildConfig.SENTRY_JAVA_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
setSdkVersion(createSdkVersion());
@@ -1047,7 +1003,7 @@ public SentryOptions() {
sdkVersion.setName(BuildConfig.SENTRY_JAVA_SDK_NAME);
String version = BuildConfig.VERSION_NAME;
sdkVersion.setVersion(version);
- sdkVersion.addPackage("maven:sentry-core", version);
+ sdkVersion.addPackage("maven:sentry", version);
return sdkVersion;
}
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryStackTraceFactory.java b/sentry/src/main/java/io/sentry/SentryStackTraceFactory.java
similarity index 97%
rename from sentry-core/src/main/java/io/sentry/core/SentryStackTraceFactory.java
rename to sentry/src/main/java/io/sentry/SentryStackTraceFactory.java
index 62e3d5419..10fe8d2fe 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryStackTraceFactory.java
+++ b/sentry/src/main/java/io/sentry/SentryStackTraceFactory.java
@@ -1,6 +1,6 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.protocol.SentryStackFrame;
+import io.sentry.protocol.SentryStackFrame;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryThreadFactory.java b/sentry/src/main/java/io/sentry/SentryThreadFactory.java
similarity index 95%
rename from sentry-core/src/main/java/io/sentry/core/SentryThreadFactory.java
rename to sentry/src/main/java/io/sentry/SentryThreadFactory.java
index fe2b223f8..ec6ce0dc1 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryThreadFactory.java
+++ b/sentry/src/main/java/io/sentry/SentryThreadFactory.java
@@ -1,9 +1,9 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.protocol.SentryStackFrame;
-import io.sentry.core.protocol.SentryStackTrace;
-import io.sentry.core.protocol.SentryThread;
-import io.sentry.core.util.Objects;
+import io.sentry.protocol.SentryStackFrame;
+import io.sentry.protocol.SentryStackTrace;
+import io.sentry.protocol.SentryThread;
+import io.sentry.util.Objects;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
diff --git a/sentry-core/src/main/java/io/sentry/core/SentryValues.java b/sentry/src/main/java/io/sentry/SentryValues.java
similarity index 94%
rename from sentry-core/src/main/java/io/sentry/core/SentryValues.java
rename to sentry/src/main/java/io/sentry/SentryValues.java
index ced625df1..ecf32e72e 100644
--- a/sentry-core/src/main/java/io/sentry/core/SentryValues.java
+++ b/sentry/src/main/java/io/sentry/SentryValues.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import java.util.ArrayList;
import java.util.List;
diff --git a/sentry-core/src/main/java/io/sentry/core/Session.java b/sentry/src/main/java/io/sentry/Session.java
similarity index 96%
rename from sentry-core/src/main/java/io/sentry/core/Session.java
rename to sentry/src/main/java/io/sentry/Session.java
index 25038666c..999ba4e20 100644
--- a/sentry-core/src/main/java/io/sentry/core/Session.java
+++ b/sentry/src/main/java/io/sentry/Session.java
@@ -1,9 +1,10 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.protocol.User;
+import io.sentry.protocol.User;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
+import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -142,6 +143,12 @@ public Session(
return init;
}
+ /** Used for migrating the init flag when an session is gonna be deleted. */
+ @ApiStatus.Internal
+ public void setInitAsTrue() {
+ this.init = true;
+ }
+
public int errorCount() {
return errorCount.get();
}
diff --git a/sentry-core/src/main/java/io/sentry/core/SessionAdapter.java b/sentry/src/main/java/io/sentry/SessionAdapter.java
similarity index 98%
rename from sentry-core/src/main/java/io/sentry/core/SessionAdapter.java
rename to sentry/src/main/java/io/sentry/SessionAdapter.java
index c463fd86a..5480b6493 100644
--- a/sentry-core/src/main/java/io/sentry/core/SessionAdapter.java
+++ b/sentry/src/main/java/io/sentry/SessionAdapter.java
@@ -1,11 +1,11 @@
-package io.sentry.core;
+package io.sentry;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
-import io.sentry.core.util.Objects;
-import io.sentry.core.util.StringUtils;
+import io.sentry.util.Objects;
+import io.sentry.util.StringUtils;
import java.io.IOException;
import java.util.Date;
import java.util.Locale;
diff --git a/sentry-core/src/main/java/io/sentry/core/ShutdownHookIntegration.java b/sentry/src/main/java/io/sentry/ShutdownHookIntegration.java
similarity index 92%
rename from sentry-core/src/main/java/io/sentry/core/ShutdownHookIntegration.java
rename to sentry/src/main/java/io/sentry/ShutdownHookIntegration.java
index b30149ba9..cc76d7b47 100644
--- a/sentry-core/src/main/java/io/sentry/core/ShutdownHookIntegration.java
+++ b/sentry/src/main/java/io/sentry/ShutdownHookIntegration.java
@@ -1,6 +1,6 @@
-package io.sentry.core;
+package io.sentry;
-import io.sentry.core.util.Objects;
+import io.sentry.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
diff --git a/sentry-core/src/main/java/io/sentry/core/SynchronizedCollection.java b/sentry/src/main/java/io/sentry/SynchronizedCollection.java
similarity index 99%
rename from sentry-core/src/main/java/io/sentry/core/SynchronizedCollection.java
rename to sentry/src/main/java/io/sentry/SynchronizedCollection.java
index 84d5ba18f..3d430952e 100644
--- a/sentry-core/src/main/java/io/sentry/core/SynchronizedCollection.java
+++ b/sentry/src/main/java/io/sentry/SynchronizedCollection.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.sentry.core;
+package io.sentry;
import com.jakewharton.nopen.annotation.Open;
import java.io.Serializable;
diff --git a/sentry-core/src/main/java/io/sentry/core/SynchronizedQueue.java b/sentry/src/main/java/io/sentry/SynchronizedQueue.java
similarity index 99%
rename from sentry-core/src/main/java/io/sentry/core/SynchronizedQueue.java
rename to sentry/src/main/java/io/sentry/SynchronizedQueue.java
index 015e35066..6236f052d 100644
--- a/sentry-core/src/main/java/io/sentry/core/SynchronizedQueue.java
+++ b/sentry/src/main/java/io/sentry/SynchronizedQueue.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.sentry.core;
+package io.sentry;
import java.util.Queue;
diff --git a/sentry-core/src/main/java/io/sentry/core/SystemOutLogger.java b/sentry/src/main/java/io/sentry/SystemOutLogger.java
similarity index 98%
rename from sentry-core/src/main/java/io/sentry/core/SystemOutLogger.java
rename to sentry/src/main/java/io/sentry/SystemOutLogger.java
index 20f7ce810..90aef4244 100644
--- a/sentry-core/src/main/java/io/sentry/core/SystemOutLogger.java
+++ b/sentry/src/main/java/io/sentry/SystemOutLogger.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import java.io.PrintWriter;
import java.io.StringWriter;
diff --git a/sentry-core/src/main/java/io/sentry/core/UncaughtExceptionHandler.java b/sentry/src/main/java/io/sentry/UncaughtExceptionHandler.java
similarity index 97%
rename from sentry-core/src/main/java/io/sentry/core/UncaughtExceptionHandler.java
rename to sentry/src/main/java/io/sentry/UncaughtExceptionHandler.java
index 4c7dcea07..74457d8d3 100644
--- a/sentry-core/src/main/java/io/sentry/core/UncaughtExceptionHandler.java
+++ b/sentry/src/main/java/io/sentry/UncaughtExceptionHandler.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
/** An adapter to make UncaughtExceptionHandler testable */
interface UncaughtExceptionHandler {
diff --git a/sentry-core/src/main/java/io/sentry/core/UncaughtExceptionHandlerIntegration.java b/sentry/src/main/java/io/sentry/UncaughtExceptionHandlerIntegration.java
similarity index 92%
rename from sentry-core/src/main/java/io/sentry/core/UncaughtExceptionHandlerIntegration.java
rename to sentry/src/main/java/io/sentry/UncaughtExceptionHandlerIntegration.java
index dc6ca5822..6ae251254 100644
--- a/sentry-core/src/main/java/io/sentry/core/UncaughtExceptionHandlerIntegration.java
+++ b/sentry/src/main/java/io/sentry/UncaughtExceptionHandlerIntegration.java
@@ -1,12 +1,13 @@
-package io.sentry.core;
+package io.sentry;
-import static io.sentry.core.SentryLevel.ERROR;
+import static io.sentry.SentryLevel.ERROR;
-import io.sentry.core.exception.ExceptionMechanismException;
-import io.sentry.core.hints.DiskFlushNotification;
-import io.sentry.core.hints.Flushable;
-import io.sentry.core.protocol.Mechanism;
-import io.sentry.core.util.Objects;
+import io.sentry.exception.ExceptionMechanismException;
+import io.sentry.hints.DiskFlushNotification;
+import io.sentry.hints.Flushable;
+import io.sentry.hints.SessionEnd;
+import io.sentry.protocol.Mechanism;
+import io.sentry.util.Objects;
import java.io.Closeable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -137,7 +138,8 @@ public void close() {
}
}
- private static final class UncaughtExceptionHint implements DiskFlushNotification, Flushable {
+ private static final class UncaughtExceptionHint
+ implements DiskFlushNotification, Flushable, SessionEnd {
private final CountDownLatch latch;
private final long flushTimeoutMillis;
diff --git a/sentry-core/src/main/java/io/sentry/core/UnknownPropertiesTypeAdapterFactory.java b/sentry/src/main/java/io/sentry/UnknownPropertiesTypeAdapterFactory.java
similarity index 99%
rename from sentry-core/src/main/java/io/sentry/core/UnknownPropertiesTypeAdapterFactory.java
rename to sentry/src/main/java/io/sentry/UnknownPropertiesTypeAdapterFactory.java
index 42717f0d1..e9c9c5b5d 100644
--- a/sentry-core/src/main/java/io/sentry/core/UnknownPropertiesTypeAdapterFactory.java
+++ b/sentry/src/main/java/io/sentry/UnknownPropertiesTypeAdapterFactory.java
@@ -1,4 +1,4 @@
-package io.sentry.core;
+package io.sentry;
import com.google.gson.FieldNamingStrategy;
import com.google.gson.Gson;
diff --git a/sentry-core/src/main/java/io/sentry/core/adapters/ContextsDeserializerAdapter.java b/sentry/src/main/java/io/sentry/adapters/ContextsDeserializerAdapter.java
similarity index 90%
rename from sentry-core/src/main/java/io/sentry/core/adapters/ContextsDeserializerAdapter.java
rename to sentry/src/main/java/io/sentry/adapters/ContextsDeserializerAdapter.java
index eaa86e60d..aaf90d90d 100644
--- a/sentry-core/src/main/java/io/sentry/core/adapters/ContextsDeserializerAdapter.java
+++ b/sentry/src/main/java/io/sentry/adapters/ContextsDeserializerAdapter.java
@@ -1,19 +1,19 @@
-package io.sentry.core.adapters;
+package io.sentry.adapters;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
-import io.sentry.core.ILogger;
-import io.sentry.core.SentryLevel;
-import io.sentry.core.protocol.App;
-import io.sentry.core.protocol.Browser;
-import io.sentry.core.protocol.Contexts;
-import io.sentry.core.protocol.Device;
-import io.sentry.core.protocol.Gpu;
-import io.sentry.core.protocol.OperatingSystem;
-import io.sentry.core.protocol.SentryRuntime;
+import io.sentry.ILogger;
+import io.sentry.SentryLevel;
+import io.sentry.protocol.App;
+import io.sentry.protocol.Browser;
+import io.sentry.protocol.Contexts;
+import io.sentry.protocol.Device;
+import io.sentry.protocol.Gpu;
+import io.sentry.protocol.OperatingSystem;
+import io.sentry.protocol.SentryRuntime;
import java.lang.reflect.Type;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
diff --git a/sentry-core/src/main/java/io/sentry/core/adapters/ContextsSerializerAdapter.java b/sentry/src/main/java/io/sentry/adapters/ContextsSerializerAdapter.java
similarity index 89%
rename from sentry-core/src/main/java/io/sentry/core/adapters/ContextsSerializerAdapter.java
rename to sentry/src/main/java/io/sentry/adapters/ContextsSerializerAdapter.java
index ef96fafd8..834c04915 100644
--- a/sentry-core/src/main/java/io/sentry/core/adapters/ContextsSerializerAdapter.java
+++ b/sentry/src/main/java/io/sentry/adapters/ContextsSerializerAdapter.java
@@ -1,13 +1,13 @@
-package io.sentry.core.adapters;
+package io.sentry.adapters;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
-import io.sentry.core.ILogger;
-import io.sentry.core.SentryLevel;
-import io.sentry.core.protocol.Contexts;
+import io.sentry.ILogger;
+import io.sentry.SentryLevel;
+import io.sentry.protocol.Contexts;
import java.lang.reflect.Type;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/adapters/DateDeserializerAdapter.java b/sentry/src/main/java/io/sentry/adapters/DateDeserializerAdapter.java
similarity index 90%
rename from sentry-core/src/main/java/io/sentry/core/adapters/DateDeserializerAdapter.java
rename to sentry/src/main/java/io/sentry/adapters/DateDeserializerAdapter.java
index ed0e520d6..d5331f561 100644
--- a/sentry-core/src/main/java/io/sentry/core/adapters/DateDeserializerAdapter.java
+++ b/sentry/src/main/java/io/sentry/adapters/DateDeserializerAdapter.java
@@ -1,12 +1,12 @@
-package io.sentry.core.adapters;
+package io.sentry.adapters;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
-import io.sentry.core.DateUtils;
-import io.sentry.core.ILogger;
-import io.sentry.core.SentryLevel;
+import io.sentry.DateUtils;
+import io.sentry.ILogger;
+import io.sentry.SentryLevel;
import java.lang.reflect.Type;
import java.util.Date;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/adapters/DateSerializerAdapter.java b/sentry/src/main/java/io/sentry/adapters/DateSerializerAdapter.java
similarity index 86%
rename from sentry-core/src/main/java/io/sentry/core/adapters/DateSerializerAdapter.java
rename to sentry/src/main/java/io/sentry/adapters/DateSerializerAdapter.java
index 3a7e2ae61..419b57917 100644
--- a/sentry-core/src/main/java/io/sentry/core/adapters/DateSerializerAdapter.java
+++ b/sentry/src/main/java/io/sentry/adapters/DateSerializerAdapter.java
@@ -1,12 +1,12 @@
-package io.sentry.core.adapters;
+package io.sentry.adapters;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
-import io.sentry.core.DateUtils;
-import io.sentry.core.ILogger;
-import io.sentry.core.SentryLevel;
+import io.sentry.DateUtils;
+import io.sentry.ILogger;
+import io.sentry.SentryLevel;
import java.lang.reflect.Type;
import java.util.Date;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/adapters/OrientationDeserializerAdapter.java b/sentry/src/main/java/io/sentry/adapters/OrientationDeserializerAdapter.java
similarity index 88%
rename from sentry-core/src/main/java/io/sentry/core/adapters/OrientationDeserializerAdapter.java
rename to sentry/src/main/java/io/sentry/adapters/OrientationDeserializerAdapter.java
index 53d3a1d8d..d4fd295ec 100644
--- a/sentry-core/src/main/java/io/sentry/core/adapters/OrientationDeserializerAdapter.java
+++ b/sentry/src/main/java/io/sentry/adapters/OrientationDeserializerAdapter.java
@@ -1,12 +1,12 @@
-package io.sentry.core.adapters;
+package io.sentry.adapters;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
-import io.sentry.core.ILogger;
-import io.sentry.core.SentryLevel;
-import io.sentry.core.protocol.Device;
+import io.sentry.ILogger;
+import io.sentry.SentryLevel;
+import io.sentry.protocol.Device;
import java.lang.reflect.Type;
import java.util.Locale;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/adapters/OrientationSerializerAdapter.java b/sentry/src/main/java/io/sentry/adapters/OrientationSerializerAdapter.java
similarity index 87%
rename from sentry-core/src/main/java/io/sentry/core/adapters/OrientationSerializerAdapter.java
rename to sentry/src/main/java/io/sentry/adapters/OrientationSerializerAdapter.java
index 9a7c97ed3..4e1ee6c72 100644
--- a/sentry-core/src/main/java/io/sentry/core/adapters/OrientationSerializerAdapter.java
+++ b/sentry/src/main/java/io/sentry/adapters/OrientationSerializerAdapter.java
@@ -1,12 +1,12 @@
-package io.sentry.core.adapters;
+package io.sentry.adapters;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
-import io.sentry.core.ILogger;
-import io.sentry.core.SentryLevel;
-import io.sentry.core.protocol.Device;
+import io.sentry.ILogger;
+import io.sentry.SentryLevel;
+import io.sentry.protocol.Device;
import java.lang.reflect.Type;
import java.util.Locale;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/adapters/SentryIdDeserializerAdapter.java b/sentry/src/main/java/io/sentry/adapters/SentryIdDeserializerAdapter.java
similarity index 86%
rename from sentry-core/src/main/java/io/sentry/core/adapters/SentryIdDeserializerAdapter.java
rename to sentry/src/main/java/io/sentry/adapters/SentryIdDeserializerAdapter.java
index 10c0b77dd..83ed4193b 100644
--- a/sentry-core/src/main/java/io/sentry/core/adapters/SentryIdDeserializerAdapter.java
+++ b/sentry/src/main/java/io/sentry/adapters/SentryIdDeserializerAdapter.java
@@ -1,12 +1,12 @@
-package io.sentry.core.adapters;
+package io.sentry.adapters;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
-import io.sentry.core.ILogger;
-import io.sentry.core.SentryLevel;
-import io.sentry.core.protocol.SentryId;
+import io.sentry.ILogger;
+import io.sentry.SentryLevel;
+import io.sentry.protocol.SentryId;
import java.lang.reflect.Type;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
diff --git a/sentry-core/src/main/java/io/sentry/core/adapters/SentryIdSerializerAdapter.java b/sentry/src/main/java/io/sentry/adapters/SentryIdSerializerAdapter.java
similarity index 85%
rename from sentry-core/src/main/java/io/sentry/core/adapters/SentryIdSerializerAdapter.java
rename to sentry/src/main/java/io/sentry/adapters/SentryIdSerializerAdapter.java
index 251b3ccba..7cf6efab4 100644
--- a/sentry-core/src/main/java/io/sentry/core/adapters/SentryIdSerializerAdapter.java
+++ b/sentry/src/main/java/io/sentry/adapters/SentryIdSerializerAdapter.java
@@ -1,12 +1,12 @@
-package io.sentry.core.adapters;
+package io.sentry.adapters;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
-import io.sentry.core.ILogger;
-import io.sentry.core.SentryLevel;
-import io.sentry.core.protocol.SentryId;
+import io.sentry.ILogger;
+import io.sentry.SentryLevel;
+import io.sentry.protocol.SentryId;
import java.lang.reflect.Type;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
diff --git a/sentry-core/src/main/java/io/sentry/core/adapters/SentryLevelDeserializerAdapter.java b/sentry/src/main/java/io/sentry/adapters/SentryLevelDeserializerAdapter.java
similarity index 90%
rename from sentry-core/src/main/java/io/sentry/core/adapters/SentryLevelDeserializerAdapter.java
rename to sentry/src/main/java/io/sentry/adapters/SentryLevelDeserializerAdapter.java
index 8018a64e3..09f109d5b 100644
--- a/sentry-core/src/main/java/io/sentry/core/adapters/SentryLevelDeserializerAdapter.java
+++ b/sentry/src/main/java/io/sentry/adapters/SentryLevelDeserializerAdapter.java
@@ -1,11 +1,11 @@
-package io.sentry.core.adapters;
+package io.sentry.adapters;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
-import io.sentry.core.ILogger;
-import io.sentry.core.SentryLevel;
+import io.sentry.ILogger;
+import io.sentry.SentryLevel;
import java.lang.reflect.Type;
import java.util.Locale;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/adapters/SentryLevelSerializerAdapter.java b/sentry/src/main/java/io/sentry/adapters/SentryLevelSerializerAdapter.java
similarity index 90%
rename from sentry-core/src/main/java/io/sentry/core/adapters/SentryLevelSerializerAdapter.java
rename to sentry/src/main/java/io/sentry/adapters/SentryLevelSerializerAdapter.java
index fb5a383a3..911f83015 100644
--- a/sentry-core/src/main/java/io/sentry/core/adapters/SentryLevelSerializerAdapter.java
+++ b/sentry/src/main/java/io/sentry/adapters/SentryLevelSerializerAdapter.java
@@ -1,11 +1,11 @@
-package io.sentry.core.adapters;
+package io.sentry.adapters;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
-import io.sentry.core.ILogger;
-import io.sentry.core.SentryLevel;
+import io.sentry.ILogger;
+import io.sentry.SentryLevel;
import java.lang.reflect.Type;
import java.util.Locale;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/adapters/TimeZoneDeserializerAdapter.java b/sentry/src/main/java/io/sentry/adapters/TimeZoneDeserializerAdapter.java
similarity index 90%
rename from sentry-core/src/main/java/io/sentry/core/adapters/TimeZoneDeserializerAdapter.java
rename to sentry/src/main/java/io/sentry/adapters/TimeZoneDeserializerAdapter.java
index 6d7ecaeb2..2cd89b4fb 100644
--- a/sentry-core/src/main/java/io/sentry/core/adapters/TimeZoneDeserializerAdapter.java
+++ b/sentry/src/main/java/io/sentry/adapters/TimeZoneDeserializerAdapter.java
@@ -1,11 +1,11 @@
-package io.sentry.core.adapters;
+package io.sentry.adapters;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
-import io.sentry.core.ILogger;
-import io.sentry.core.SentryLevel;
+import io.sentry.ILogger;
+import io.sentry.SentryLevel;
import java.lang.reflect.Type;
import java.util.TimeZone;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/adapters/TimeZoneSerializerAdapter.java b/sentry/src/main/java/io/sentry/adapters/TimeZoneSerializerAdapter.java
similarity index 89%
rename from sentry-core/src/main/java/io/sentry/core/adapters/TimeZoneSerializerAdapter.java
rename to sentry/src/main/java/io/sentry/adapters/TimeZoneSerializerAdapter.java
index 6e7009d99..4c5356229 100644
--- a/sentry-core/src/main/java/io/sentry/core/adapters/TimeZoneSerializerAdapter.java
+++ b/sentry/src/main/java/io/sentry/adapters/TimeZoneSerializerAdapter.java
@@ -1,11 +1,11 @@
-package io.sentry.core.adapters;
+package io.sentry.adapters;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
-import io.sentry.core.ILogger;
-import io.sentry.core.SentryLevel;
+import io.sentry.ILogger;
+import io.sentry.SentryLevel;
import java.lang.reflect.Type;
import java.util.TimeZone;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry/src/main/java/io/sentry/cache/CacheStrategy.java b/sentry/src/main/java/io/sentry/cache/CacheStrategy.java
new file mode 100644
index 000000000..40d36c585
--- /dev/null
+++ b/sentry/src/main/java/io/sentry/cache/CacheStrategy.java
@@ -0,0 +1,306 @@
+package io.sentry.cache;
+
+import static io.sentry.SentryLevel.ERROR;
+
+import io.sentry.ISerializer;
+import io.sentry.SentryEnvelope;
+import io.sentry.SentryEnvelopeItem;
+import io.sentry.SentryItemType;
+import io.sentry.SentryLevel;
+import io.sentry.SentryOptions;
+import io.sentry.Session;
+import io.sentry.util.Objects;
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.UUID;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+abstract class CacheStrategy {
+
+ @SuppressWarnings("CharsetObjectCanBeUsed")
+ protected static final Charset UTF_8 = Charset.forName("UTF-8");
+
+ protected final @NotNull SentryOptions options;
+ protected final @NotNull ISerializer serializer;
+ protected final @NotNull File directory;
+ private final int maxSize;
+
+ CacheStrategy(
+ final @NotNull SentryOptions options,
+ final @NotNull String directoryPath,
+ final int maxSize) {
+ Objects.requireNonNull(directoryPath, "Directory is required.");
+ this.options = Objects.requireNonNull(options, "SentryOptions is required.");
+
+ this.serializer = options.getSerializer();
+ this.directory = new File(directoryPath);
+
+ this.maxSize = maxSize;
+ }
+
+ /**
+ * Check if a dir. is valid and have write and read permission
+ *
+ * @return true if valid and has permissions or false otherwise
+ */
+ protected boolean isDirectoryValid() {
+ if (!directory.isDirectory() || !directory.canWrite() || !directory.canRead()) {
+ options
+ .getLogger()
+ .log(
+ ERROR,
+ "The directory for caching files is inaccessible.: %s",
+ directory.getAbsolutePath());
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Sort files from oldest to the newest using the lastModified method
+ *
+ * @param files the Files
+ */
+ private void sortFilesOldestToNewest(@NotNull File[] files) {
+ // just sort it if more than 1 file
+ if (files.length > 1) {
+ Arrays.sort(files, (f1, f2) -> Long.compare(f1.lastModified(), f2.lastModified()));
+ }
+ }
+
+ /**
+ * Rotates the caching folder if full, deleting the oldest files first
+ *
+ * @param files the Files
+ */
+ protected void rotateCacheIfNeeded(final @NotNull File[] files) {
+ final int length = files.length;
+ if (length >= maxSize) {
+ options
+ .getLogger()
+ .log(SentryLevel.WARNING, "Cache folder if full (respecting maxSize). Rotating files");
+ final int totalToBeDeleted = (length - maxSize) + 1;
+
+ sortFilesOldestToNewest(files);
+
+ final File[] notDeletedFiles = Arrays.copyOfRange(files, totalToBeDeleted, length);
+
+ // delete files from the top of the Array as its sorted by the oldest to the newest
+ for (int i = 0; i < totalToBeDeleted; i++) {
+ final File file = files[i];
+
+ // move init flag if necessary
+ moveInitFlagIfNecessary(file, notDeletedFiles);
+
+ if (!file.delete()) {
+ options
+ .getLogger()
+ .log(SentryLevel.WARNING, "File can't be deleted: %s", file.getAbsolutePath());
+ }
+ }
+ }
+ }
+
+ private void moveInitFlagIfNecessary(
+ final @NotNull File currentFile, final @NotNull File[] notDeletedFiles) {
+ final SentryEnvelope currentEnvelope = readEnvelope(currentFile);
+
+ if (!isValidEnvelope(currentEnvelope)) {
+ return;
+ }
+
+ final Session currentSession = getFirstSession(currentEnvelope);
+
+ if (!isValidSession(currentSession)) {
+ return;
+ }
+
+ // nothing to do if its not true
+ final Boolean currentSessionInit = currentSession.getInit();
+ if (currentSessionInit == null || !currentSessionInit) {
+ return;
+ }
+
+ // we need to move the init flag
+ for (final File notDeletedFile : notDeletedFiles) {
+ final SentryEnvelope envelope = readEnvelope(notDeletedFile);
+
+ if (!isValidEnvelope(envelope)) {
+ continue;
+ }
+
+ SentryEnvelopeItem newSessionItem = null;
+ final Iterator itemsIterator = envelope.getItems().iterator();
+
+ while (itemsIterator.hasNext()) {
+ final SentryEnvelopeItem envelopeItem = itemsIterator.next();
+
+ if (!isSessionType(envelopeItem)) {
+ continue;
+ }
+
+ final Session session = readSession(envelopeItem);
+
+ if (!isValidSession(session)) {
+ continue;
+ }
+
+ final Boolean init = session.getInit();
+ if (init != null && init) {
+ options
+ .getLogger()
+ .log(ERROR, "Session %s has 2 times the init flag.", currentSession.getSessionId());
+ return;
+ }
+
+ if (currentSession.getSessionId().equals(session.getSessionId())) {
+ session.setInitAsTrue();
+ try {
+ newSessionItem = SentryEnvelopeItem.fromSession(serializer, session);
+ // remove item from envelope items so we can replace with the new one that has the
+ // init flag true
+ itemsIterator.remove();
+ } catch (IOException e) {
+ options
+ .getLogger()
+ .log(
+ ERROR,
+ e,
+ "Failed to create new envelope item for the session %s",
+ currentSession.getSessionId());
+ }
+
+ break;
+ }
+ }
+
+ if (newSessionItem != null) {
+ final SentryEnvelope newEnvelope = buildNewEnvelope(envelope, newSessionItem);
+
+ long notDeletedFileTimestamp = notDeletedFile.lastModified();
+ if (!notDeletedFile.delete()) {
+ options
+ .getLogger()
+ .log(
+ SentryLevel.WARNING,
+ "File can't be deleted: %s",
+ notDeletedFile.getAbsolutePath());
+ }
+
+ saveNewEnvelope(newEnvelope, notDeletedFile, notDeletedFileTimestamp);
+ break;
+ }
+ }
+ }
+
+ private @Nullable SentryEnvelope readEnvelope(final @NotNull File file) {
+ try (final InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
+ return serializer.deserializeEnvelope(inputStream);
+ } catch (IOException e) {
+ options.getLogger().log(ERROR, "Failed to deserialize the envelope.", e);
+ }
+
+ return null;
+ }
+
+ private @Nullable Session getFirstSession(final @NotNull SentryEnvelope envelope) {
+ for (final SentryEnvelopeItem item : envelope.getItems()) {
+ if (!isSessionType(item)) {
+ continue;
+ }
+
+ // we are assuming that there's only 1 session per envelope for now
+ return readSession(item);
+ }
+ return null;
+ }
+
+ private boolean isValidSession(final @Nullable Session session) {
+ if (session == null) {
+ return false;
+ }
+
+ if (!session.getStatus().equals(Session.State.Ok)) {
+ return false;
+ }
+
+ final UUID sessionId = session.getSessionId();
+
+ if (sessionId == null) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isSessionType(final @Nullable SentryEnvelopeItem item) {
+ if (item == null) {
+ return false;
+ }
+
+ return item.getHeader().getType().equals(SentryItemType.Session);
+ }
+
+ private @Nullable Session readSession(final @NotNull SentryEnvelopeItem item) {
+ try (final Reader reader =
+ new BufferedReader(
+ new InputStreamReader(new ByteArrayInputStream(item.getData()), UTF_8))) {
+ return serializer.deserializeSession(reader);
+ } catch (Exception e) {
+ options.getLogger().log(ERROR, "Failed to deserialize the session.", e);
+ }
+ return null;
+ }
+
+ private void saveNewEnvelope(
+ final @NotNull SentryEnvelope envelope, final @NotNull File file, final long timestamp) {
+ try (final OutputStream outputStream = new FileOutputStream(file);
+ final Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream, UTF_8))) {
+ serializer.serialize(envelope, writer);
+ // we need to set the same timestamp so the sorting from oldest to newest wont break.
+ file.setLastModified(timestamp);
+ } catch (Exception e) {
+ options.getLogger().log(ERROR, "Failed to serialize the new envelope to the disk.", e);
+ }
+ }
+
+ private @NotNull SentryEnvelope buildNewEnvelope(
+ final @NotNull SentryEnvelope envelope, final @NotNull SentryEnvelopeItem sessionItem) {
+ final List newEnvelopeItems = new ArrayList<>();
+
+ for (final SentryEnvelopeItem newEnvelopeItem : envelope.getItems()) {
+ newEnvelopeItems.add(newEnvelopeItem);
+ }
+ newEnvelopeItems.add(sessionItem);
+
+ return new SentryEnvelope(envelope.getHeader(), newEnvelopeItems);
+ }
+
+ private boolean isValidEnvelope(final @Nullable SentryEnvelope envelope) {
+ if (envelope == null) {
+ return false;
+ }
+
+ if (!envelope.getItems().iterator().hasNext()) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sentry-core/src/main/java/io/sentry/core/cache/SessionCache.java b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java
similarity index 82%
rename from sentry-core/src/main/java/io/sentry/core/cache/SessionCache.java
rename to sentry/src/main/java/io/sentry/cache/EnvelopeCache.java
index 8b872136f..347712cb7 100644
--- a/sentry-core/src/main/java/io/sentry/core/cache/SessionCache.java
+++ b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java
@@ -1,23 +1,21 @@
-package io.sentry.core.cache;
+package io.sentry.cache;
-import static io.sentry.core.SentryLevel.DEBUG;
-import static io.sentry.core.SentryLevel.ERROR;
-import static io.sentry.core.SentryLevel.INFO;
-import static io.sentry.core.SentryLevel.WARNING;
+import static io.sentry.SentryLevel.DEBUG;
+import static io.sentry.SentryLevel.ERROR;
+import static io.sentry.SentryLevel.INFO;
+import static io.sentry.SentryLevel.WARNING;
import static java.lang.String.format;
-import io.sentry.core.DateUtils;
-import io.sentry.core.ISerializer;
-import io.sentry.core.SentryEnvelope;
-import io.sentry.core.SentryEnvelopeItem;
-import io.sentry.core.SentryItemType;
-import io.sentry.core.SentryLevel;
-import io.sentry.core.SentryOptions;
-import io.sentry.core.Session;
-import io.sentry.core.hints.SessionEnd;
-import io.sentry.core.hints.SessionStart;
-import io.sentry.core.hints.SessionUpdate;
-import io.sentry.core.util.Objects;
+import io.sentry.DateUtils;
+import io.sentry.SentryEnvelope;
+import io.sentry.SentryEnvelopeItem;
+import io.sentry.SentryItemType;
+import io.sentry.SentryLevel;
+import io.sentry.SentryOptions;
+import io.sentry.Session;
+import io.sentry.hints.SessionEnd;
+import io.sentry.hints.SessionStart;
+import io.sentry.util.Objects;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
@@ -33,7 +31,6 @@
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
-import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
@@ -46,46 +43,26 @@
import org.jetbrains.annotations.Nullable;
@ApiStatus.Internal
-public final class SessionCache implements IEnvelopeCache {
+public final class EnvelopeCache extends CacheStrategy implements IEnvelopeCache {
/** File suffix added to all serialized envelopes files. */
- static final String SUFFIX_ENVELOPE_FILE = ".envelope";
+ public static final String SUFFIX_ENVELOPE_FILE = ".envelope";
public static final String PREFIX_CURRENT_SESSION_FILE = "session";
static final String SUFFIX_CURRENT_SESSION_FILE = ".json";
static final String CRASH_MARKER_FILE = ".sentry-native/last_crash";
- @SuppressWarnings("CharsetObjectCanBeUsed")
- private static final Charset UTF_8 = Charset.forName("UTF-8");
-
- private final @NotNull File directory;
- private final int maxSize;
- private final @NotNull ISerializer serializer;
- private final @NotNull SentryOptions options;
-
private final @NotNull Map fileNameMap = new WeakHashMap<>();
- public SessionCache(final @NotNull SentryOptions options) {
- Objects.requireNonNull(options.getSessionsPath(), "sessions dir. path is required.");
- this.directory = new File(options.getSessionsPath());
- this.maxSize = options.getSessionsDirSize();
- this.serializer = options.getSerializer();
- this.options = options;
+ public EnvelopeCache(final @NotNull SentryOptions options) {
+ super(options, options.getCacheDirPath(), options.getCacheDirSize());
}
@Override
public void store(final @NotNull SentryEnvelope envelope, final @Nullable Object hint) {
Objects.requireNonNull(envelope, "Envelope is required.");
- if (getNumberOfStoredEnvelopes() >= maxSize) {
- options
- .getLogger()
- .log(
- SentryLevel.WARNING,
- "Disk cache full (respecting maxSize). Not storing envelope {}",
- envelope);
- return;
- }
+ rotateCacheIfNeeded(allEnvelopeFiles());
final File currentSessionFile = getCurrentSessionFile();
@@ -156,10 +133,8 @@ public void store(final @NotNull SentryEnvelope envelope, final @Nullable Object
updateCurrentSession(currentSessionFile, envelope);
}
- if (hint instanceof SessionUpdate) {
- updateCurrentSession(currentSessionFile, envelope);
- return;
- }
+ // TODO: probably we need to update the current session file for session updates to because of
+ // hardcrash events
final File envelopeFile = getEnvelopeFile(envelope);
if (envelopeFile.exists()) {
@@ -185,7 +160,7 @@ public void store(final @NotNull SentryEnvelope envelope, final @Nullable Object
* @param markerFile the marker file
* @return the timestamp as Date
*/
- private Date getTimestampFromCrashMarkerFile(final @NotNull File markerFile) {
+ private @Nullable Date getTimestampFromCrashMarkerFile(final @NotNull File markerFile) {
try (final BufferedReader reader =
new BufferedReader(new InputStreamReader(new FileInputStream(markerFile), UTF_8))) {
final String timestamp = reader.readLine();
@@ -301,23 +276,6 @@ public void discard(final @NotNull SentryEnvelope envelope) {
}
}
- private int getNumberOfStoredEnvelopes() {
- return allEnvelopeFiles().length;
- }
-
- private boolean isDirectoryValid() {
- if (!directory.isDirectory() || !directory.canWrite() || !directory.canRead()) {
- options
- .getLogger()
- .log(
- ERROR,
- "The directory for caching Sentry envelopes is inaccessible.: %s",
- directory.getAbsolutePath());
- return false;
- }
- return true;
- }
-
/**
* Returns the envelope's file path. If the envelope has no eventId header, it generates a random
* file name to it.
@@ -380,7 +338,11 @@ private boolean isDirectoryValid() {
private @NotNull File[] allEnvelopeFiles() {
if (isDirectoryValid()) {
// lets filter the session.json here
- return directory.listFiles((__, fileName) -> fileName.endsWith(SUFFIX_ENVELOPE_FILE));
+ final File[] files =
+ directory.listFiles((__, fileName) -> fileName.endsWith(SUFFIX_ENVELOPE_FILE));
+ if (files != null) {
+ return files;
+ }
}
return new File[] {};
}
diff --git a/sentry-core/src/main/java/io/sentry/core/cache/IEnvelopeCache.java b/sentry/src/main/java/io/sentry/cache/IEnvelopeCache.java
similarity index 81%
rename from sentry-core/src/main/java/io/sentry/core/cache/IEnvelopeCache.java
rename to sentry/src/main/java/io/sentry/cache/IEnvelopeCache.java
index eac573080..a674c7f77 100644
--- a/sentry-core/src/main/java/io/sentry/core/cache/IEnvelopeCache.java
+++ b/sentry/src/main/java/io/sentry/cache/IEnvelopeCache.java
@@ -1,6 +1,6 @@
-package io.sentry.core.cache;
+package io.sentry.cache;
-import io.sentry.core.SentryEnvelope;
+import io.sentry.SentryEnvelope;
import org.jetbrains.annotations.Nullable;
public interface IEnvelopeCache extends Iterable {
diff --git a/sentry-core/src/main/java/io/sentry/core/exception/ExceptionMechanismException.java b/sentry/src/main/java/io/sentry/exception/ExceptionMechanismException.java
similarity index 83%
rename from sentry-core/src/main/java/io/sentry/core/exception/ExceptionMechanismException.java
rename to sentry/src/main/java/io/sentry/exception/ExceptionMechanismException.java
index b7f852232..5d7914c98 100644
--- a/sentry-core/src/main/java/io/sentry/core/exception/ExceptionMechanismException.java
+++ b/sentry/src/main/java/io/sentry/exception/ExceptionMechanismException.java
@@ -1,12 +1,12 @@
-package io.sentry.core.exception;
+package io.sentry.exception;
-import io.sentry.core.protocol.Mechanism;
+import io.sentry.protocol.Mechanism;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
/**
- * A throwable decorator that holds an {@link io.sentry.core.protocol.Mechanism} related to the
- * decorated {@link Throwable}.
+ * A throwable decorator that holds an {@link io.sentry.protocol.Mechanism} related to the decorated
+ * {@link Throwable}.
*/
@ApiStatus.Internal
public final class ExceptionMechanismException extends RuntimeException {
diff --git a/sentry-core/src/main/java/io/sentry/core/hints/ApplyScopeData.java b/sentry/src/main/java/io/sentry/hints/ApplyScopeData.java
similarity index 86%
rename from sentry-core/src/main/java/io/sentry/core/hints/ApplyScopeData.java
rename to sentry/src/main/java/io/sentry/hints/ApplyScopeData.java
index c3d4997c1..91da65bd8 100644
--- a/sentry-core/src/main/java/io/sentry/core/hints/ApplyScopeData.java
+++ b/sentry/src/main/java/io/sentry/hints/ApplyScopeData.java
@@ -1,4 +1,4 @@
-package io.sentry.core.hints;
+package io.sentry.hints;
/**
* Marker interface for applying scope's data. This means applying data relevant to the current
diff --git a/sentry-core/src/main/java/io/sentry/core/hints/Cached.java b/sentry/src/main/java/io/sentry/hints/Cached.java
similarity index 76%
rename from sentry-core/src/main/java/io/sentry/core/hints/Cached.java
rename to sentry/src/main/java/io/sentry/hints/Cached.java
index 100f90695..d3bacd850 100644
--- a/sentry-core/src/main/java/io/sentry/core/hints/Cached.java
+++ b/sentry/src/main/java/io/sentry/hints/Cached.java
@@ -1,4 +1,4 @@
-package io.sentry.core.hints;
+package io.sentry.hints;
/** Marker interface for a capture involving data cached from disk */
public interface Cached {}
diff --git a/sentry-core/src/main/java/io/sentry/core/hints/DiskFlushNotification.java b/sentry/src/main/java/io/sentry/hints/DiskFlushNotification.java
similarity index 68%
rename from sentry-core/src/main/java/io/sentry/core/hints/DiskFlushNotification.java
rename to sentry/src/main/java/io/sentry/hints/DiskFlushNotification.java
index 7ade8f018..52d32e850 100644
--- a/sentry-core/src/main/java/io/sentry/core/hints/DiskFlushNotification.java
+++ b/sentry/src/main/java/io/sentry/hints/DiskFlushNotification.java
@@ -1,4 +1,4 @@
-package io.sentry.core.hints;
+package io.sentry.hints;
public interface DiskFlushNotification {
void markFlushed();
diff --git a/sentry-core/src/main/java/io/sentry/core/hints/Flushable.java b/sentry/src/main/java/io/sentry/hints/Flushable.java
similarity index 79%
rename from sentry-core/src/main/java/io/sentry/core/hints/Flushable.java
rename to sentry/src/main/java/io/sentry/hints/Flushable.java
index 970bc1df1..f5348dabf 100644
--- a/sentry-core/src/main/java/io/sentry/core/hints/Flushable.java
+++ b/sentry/src/main/java/io/sentry/hints/Flushable.java
@@ -1,4 +1,4 @@
-package io.sentry.core.hints;
+package io.sentry.hints;
/** marker interface that awaits events to be flushed */
public interface Flushable {
diff --git a/sentry-core/src/main/java/io/sentry/core/hints/Retryable.java b/sentry/src/main/java/io/sentry/hints/Retryable.java
similarity index 74%
rename from sentry-core/src/main/java/io/sentry/core/hints/Retryable.java
rename to sentry/src/main/java/io/sentry/hints/Retryable.java
index c9a57ea20..51bf85d33 100644
--- a/sentry-core/src/main/java/io/sentry/core/hints/Retryable.java
+++ b/sentry/src/main/java/io/sentry/hints/Retryable.java
@@ -1,4 +1,4 @@
-package io.sentry.core.hints;
+package io.sentry.hints;
public interface Retryable {
boolean isRetry();
diff --git a/sentry-core/src/main/java/io/sentry/core/hints/SessionEnd.java b/sentry/src/main/java/io/sentry/hints/SessionEnd.java
similarity index 74%
rename from sentry-core/src/main/java/io/sentry/core/hints/SessionEnd.java
rename to sentry/src/main/java/io/sentry/hints/SessionEnd.java
index 84ea9a7b8..59d7f120e 100644
--- a/sentry-core/src/main/java/io/sentry/core/hints/SessionEnd.java
+++ b/sentry/src/main/java/io/sentry/hints/SessionEnd.java
@@ -1,4 +1,4 @@
-package io.sentry.core.hints;
+package io.sentry.hints;
/** Hint that shows this is a session end envelope */
public interface SessionEnd {}
diff --git a/sentry-core/src/main/java/io/sentry/core/hints/SessionEndHint.java b/sentry/src/main/java/io/sentry/hints/SessionEndHint.java
similarity index 66%
rename from sentry-core/src/main/java/io/sentry/core/hints/SessionEndHint.java
rename to sentry/src/main/java/io/sentry/hints/SessionEndHint.java
index fdccfe95e..c083560da 100644
--- a/sentry-core/src/main/java/io/sentry/core/hints/SessionEndHint.java
+++ b/sentry/src/main/java/io/sentry/hints/SessionEndHint.java
@@ -1,3 +1,3 @@
-package io.sentry.core.hints;
+package io.sentry.hints;
public final class SessionEndHint implements SessionEnd {}
diff --git a/sentry-core/src/main/java/io/sentry/core/hints/SessionStart.java b/sentry/src/main/java/io/sentry/hints/SessionStart.java
similarity index 75%
rename from sentry-core/src/main/java/io/sentry/core/hints/SessionStart.java
rename to sentry/src/main/java/io/sentry/hints/SessionStart.java
index d9d7d6c08..6fdd7f9dd 100644
--- a/sentry-core/src/main/java/io/sentry/core/hints/SessionStart.java
+++ b/sentry/src/main/java/io/sentry/hints/SessionStart.java
@@ -1,4 +1,4 @@
-package io.sentry.core.hints;
+package io.sentry.hints;
/** Hint that shows this is a session start envelope */
public interface SessionStart {}
diff --git a/sentry-core/src/main/java/io/sentry/core/hints/SessionStartHint.java b/sentry/src/main/java/io/sentry/hints/SessionStartHint.java
similarity index 68%
rename from sentry-core/src/main/java/io/sentry/core/hints/SessionStartHint.java
rename to sentry/src/main/java/io/sentry/hints/SessionStartHint.java
index f8479553a..b59db1fe5 100644
--- a/sentry-core/src/main/java/io/sentry/core/hints/SessionStartHint.java
+++ b/sentry/src/main/java/io/sentry/hints/SessionStartHint.java
@@ -1,3 +1,3 @@
-package io.sentry.core.hints;
+package io.sentry.hints;
public final class SessionStartHint implements SessionStart {}
diff --git a/sentry-core/src/main/java/io/sentry/core/hints/SubmissionResult.java b/sentry/src/main/java/io/sentry/hints/SubmissionResult.java
similarity index 76%
rename from sentry-core/src/main/java/io/sentry/core/hints/SubmissionResult.java
rename to sentry/src/main/java/io/sentry/hints/SubmissionResult.java
index 49993b549..ff25fa643 100644
--- a/sentry-core/src/main/java/io/sentry/core/hints/SubmissionResult.java
+++ b/sentry/src/main/java/io/sentry/hints/SubmissionResult.java
@@ -1,4 +1,4 @@
-package io.sentry.core.hints;
+package io.sentry.hints;
public interface SubmissionResult {
void setResult(boolean success);
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/App.java b/sentry/src/main/java/io/sentry/protocol/App.java
similarity index 94%
rename from sentry-core/src/main/java/io/sentry/core/protocol/App.java
rename to sentry/src/main/java/io/sentry/protocol/App.java
index bfc9abca0..6033b0d9c 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/App.java
+++ b/sentry/src/main/java/io/sentry/protocol/App.java
@@ -1,7 +1,7 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
-import io.sentry.core.util.CollectionUtils;
+import io.sentry.IUnknownPropertiesConsumer;
+import io.sentry.util.CollectionUtils;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/Browser.java b/sentry/src/main/java/io/sentry/protocol/Browser.java
similarity index 91%
rename from sentry-core/src/main/java/io/sentry/core/protocol/Browser.java
rename to sentry/src/main/java/io/sentry/protocol/Browser.java
index b0fa97ebd..e3c296d31 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/Browser.java
+++ b/sentry/src/main/java/io/sentry/protocol/Browser.java
@@ -1,7 +1,7 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
-import io.sentry.core.util.CollectionUtils;
+import io.sentry.IUnknownPropertiesConsumer;
+import io.sentry.util.CollectionUtils;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/Contexts.java b/sentry/src/main/java/io/sentry/protocol/Contexts.java
similarity index 98%
rename from sentry-core/src/main/java/io/sentry/core/protocol/Contexts.java
rename to sentry/src/main/java/io/sentry/protocol/Contexts.java
index 7be1aebca..f2dd6d246 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/Contexts.java
+++ b/sentry/src/main/java/io/sentry/protocol/Contexts.java
@@ -1,4 +1,4 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/DebugImage.java b/sentry/src/main/java/io/sentry/protocol/DebugImage.java
similarity index 95%
rename from sentry-core/src/main/java/io/sentry/core/protocol/DebugImage.java
rename to sentry/src/main/java/io/sentry/protocol/DebugImage.java
index a66ec30fe..598985ceb 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/DebugImage.java
+++ b/sentry/src/main/java/io/sentry/protocol/DebugImage.java
@@ -1,6 +1,6 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
+import io.sentry.IUnknownPropertiesConsumer;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/DebugMeta.java b/sentry/src/main/java/io/sentry/protocol/DebugMeta.java
similarity index 89%
rename from sentry-core/src/main/java/io/sentry/core/protocol/DebugMeta.java
rename to sentry/src/main/java/io/sentry/protocol/DebugMeta.java
index 8000421e3..822ace34d 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/DebugMeta.java
+++ b/sentry/src/main/java/io/sentry/protocol/DebugMeta.java
@@ -1,6 +1,6 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
+import io.sentry.IUnknownPropertiesConsumer;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/Device.java b/sentry/src/main/java/io/sentry/protocol/Device.java
similarity index 98%
rename from sentry-core/src/main/java/io/sentry/core/protocol/Device.java
rename to sentry/src/main/java/io/sentry/protocol/Device.java
index f11fd8da3..06ddf0e11 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/Device.java
+++ b/sentry/src/main/java/io/sentry/protocol/Device.java
@@ -1,7 +1,7 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
-import io.sentry.core.util.CollectionUtils;
+import io.sentry.IUnknownPropertiesConsumer;
+import io.sentry.util.CollectionUtils;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/Gpu.java b/sentry/src/main/java/io/sentry/protocol/Gpu.java
similarity index 95%
rename from sentry-core/src/main/java/io/sentry/core/protocol/Gpu.java
rename to sentry/src/main/java/io/sentry/protocol/Gpu.java
index ec8d28b51..f407512d1 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/Gpu.java
+++ b/sentry/src/main/java/io/sentry/protocol/Gpu.java
@@ -1,7 +1,7 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
-import io.sentry.core.util.CollectionUtils;
+import io.sentry.IUnknownPropertiesConsumer;
+import io.sentry.util.CollectionUtils;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/Mechanism.java b/sentry/src/main/java/io/sentry/protocol/Mechanism.java
similarity index 95%
rename from sentry-core/src/main/java/io/sentry/core/protocol/Mechanism.java
rename to sentry/src/main/java/io/sentry/protocol/Mechanism.java
index fd5c8b4b7..e10bc01b7 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/Mechanism.java
+++ b/sentry/src/main/java/io/sentry/protocol/Mechanism.java
@@ -1,6 +1,6 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
+import io.sentry.IUnknownPropertiesConsumer;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/Message.java b/sentry/src/main/java/io/sentry/protocol/Message.java
similarity index 92%
rename from sentry-core/src/main/java/io/sentry/core/protocol/Message.java
rename to sentry/src/main/java/io/sentry/protocol/Message.java
index 96d95d788..001c5c5bf 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/Message.java
+++ b/sentry/src/main/java/io/sentry/protocol/Message.java
@@ -1,6 +1,6 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
+import io.sentry.IUnknownPropertiesConsumer;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/OperatingSystem.java b/sentry/src/main/java/io/sentry/protocol/OperatingSystem.java
similarity index 94%
rename from sentry-core/src/main/java/io/sentry/core/protocol/OperatingSystem.java
rename to sentry/src/main/java/io/sentry/protocol/OperatingSystem.java
index 53be02361..54272d405 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/OperatingSystem.java
+++ b/sentry/src/main/java/io/sentry/protocol/OperatingSystem.java
@@ -1,7 +1,7 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
-import io.sentry.core.util.CollectionUtils;
+import io.sentry.IUnknownPropertiesConsumer;
+import io.sentry.util.CollectionUtils;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/Request.java b/sentry/src/main/java/io/sentry/protocol/Request.java
similarity index 95%
rename from sentry-core/src/main/java/io/sentry/core/protocol/Request.java
rename to sentry/src/main/java/io/sentry/protocol/Request.java
index 141d1e55a..8e6b539a9 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/Request.java
+++ b/sentry/src/main/java/io/sentry/protocol/Request.java
@@ -1,6 +1,6 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
+import io.sentry.IUnknownPropertiesConsumer;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/SdkInfo.java b/sentry/src/main/java/io/sentry/protocol/SdkInfo.java
similarity index 93%
rename from sentry-core/src/main/java/io/sentry/core/protocol/SdkInfo.java
rename to sentry/src/main/java/io/sentry/protocol/SdkInfo.java
index 892db05f0..547c4cff1 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/SdkInfo.java
+++ b/sentry/src/main/java/io/sentry/protocol/SdkInfo.java
@@ -1,6 +1,6 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
+import io.sentry.IUnknownPropertiesConsumer;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/SdkVersion.java b/sentry/src/main/java/io/sentry/protocol/SdkVersion.java
similarity index 94%
rename from sentry-core/src/main/java/io/sentry/core/protocol/SdkVersion.java
rename to sentry/src/main/java/io/sentry/protocol/SdkVersion.java
index 4621be33d..b37a44227 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/SdkVersion.java
+++ b/sentry/src/main/java/io/sentry/protocol/SdkVersion.java
@@ -1,6 +1,6 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
+import io.sentry.IUnknownPropertiesConsumer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/SentryException.java b/sentry/src/main/java/io/sentry/protocol/SentryException.java
similarity index 96%
rename from sentry-core/src/main/java/io/sentry/core/protocol/SentryException.java
rename to sentry/src/main/java/io/sentry/protocol/SentryException.java
index e61df0029..2beb26bbe 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/SentryException.java
+++ b/sentry/src/main/java/io/sentry/protocol/SentryException.java
@@ -1,6 +1,6 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
+import io.sentry.IUnknownPropertiesConsumer;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/SentryId.java b/sentry/src/main/java/io/sentry/protocol/SentryId.java
similarity index 98%
rename from sentry-core/src/main/java/io/sentry/core/protocol/SentryId.java
rename to sentry/src/main/java/io/sentry/protocol/SentryId.java
index 6fc32b754..7606e0d44 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/SentryId.java
+++ b/sentry/src/main/java/io/sentry/protocol/SentryId.java
@@ -1,4 +1,4 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/SentryPackage.java b/sentry/src/main/java/io/sentry/protocol/SentryPackage.java
similarity index 88%
rename from sentry-core/src/main/java/io/sentry/core/protocol/SentryPackage.java
rename to sentry/src/main/java/io/sentry/protocol/SentryPackage.java
index 646de45e9..01cb92847 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/SentryPackage.java
+++ b/sentry/src/main/java/io/sentry/protocol/SentryPackage.java
@@ -1,6 +1,6 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
+import io.sentry.IUnknownPropertiesConsumer;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/SentryRuntime.java b/sentry/src/main/java/io/sentry/protocol/SentryRuntime.java
similarity index 92%
rename from sentry-core/src/main/java/io/sentry/core/protocol/SentryRuntime.java
rename to sentry/src/main/java/io/sentry/protocol/SentryRuntime.java
index e5d6735ca..45e5b322a 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/SentryRuntime.java
+++ b/sentry/src/main/java/io/sentry/protocol/SentryRuntime.java
@@ -1,7 +1,7 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
-import io.sentry.core.util.CollectionUtils;
+import io.sentry.IUnknownPropertiesConsumer;
+import io.sentry.util.CollectionUtils;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/SentryStackFrame.java b/sentry/src/main/java/io/sentry/protocol/SentryStackFrame.java
similarity index 97%
rename from sentry-core/src/main/java/io/sentry/core/protocol/SentryStackFrame.java
rename to sentry/src/main/java/io/sentry/protocol/SentryStackFrame.java
index dbb328ab5..5d39a2a83 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/SentryStackFrame.java
+++ b/sentry/src/main/java/io/sentry/protocol/SentryStackFrame.java
@@ -1,7 +1,7 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
import com.google.gson.annotations.SerializedName;
-import io.sentry.core.IUnknownPropertiesConsumer;
+import io.sentry.IUnknownPropertiesConsumer;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/SentryStackTrace.java b/sentry/src/main/java/io/sentry/protocol/SentryStackTrace.java
similarity index 93%
rename from sentry-core/src/main/java/io/sentry/core/protocol/SentryStackTrace.java
rename to sentry/src/main/java/io/sentry/protocol/SentryStackTrace.java
index 874f1469a..b70a03f8b 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/SentryStackTrace.java
+++ b/sentry/src/main/java/io/sentry/protocol/SentryStackTrace.java
@@ -1,6 +1,6 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
+import io.sentry.IUnknownPropertiesConsumer;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/SentryThread.java b/sentry/src/main/java/io/sentry/protocol/SentryThread.java
similarity index 97%
rename from sentry-core/src/main/java/io/sentry/core/protocol/SentryThread.java
rename to sentry/src/main/java/io/sentry/protocol/SentryThread.java
index 37ef583be..88e9345b9 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/SentryThread.java
+++ b/sentry/src/main/java/io/sentry/protocol/SentryThread.java
@@ -1,6 +1,6 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
+import io.sentry.IUnknownPropertiesConsumer;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/protocol/User.java b/sentry/src/main/java/io/sentry/protocol/User.java
similarity index 96%
rename from sentry-core/src/main/java/io/sentry/core/protocol/User.java
rename to sentry/src/main/java/io/sentry/protocol/User.java
index 911624096..7cb42ccc3 100644
--- a/sentry-core/src/main/java/io/sentry/core/protocol/User.java
+++ b/sentry/src/main/java/io/sentry/protocol/User.java
@@ -1,7 +1,7 @@
-package io.sentry.core.protocol;
+package io.sentry.protocol;
-import io.sentry.core.IUnknownPropertiesConsumer;
-import io.sentry.core.util.CollectionUtils;
+import io.sentry.IUnknownPropertiesConsumer;
+import io.sentry.util.CollectionUtils;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/transport/AsyncConnection.java b/sentry/src/main/java/io/sentry/transport/AsyncConnection.java
similarity index 54%
rename from sentry-core/src/main/java/io/sentry/core/transport/AsyncConnection.java
rename to sentry/src/main/java/io/sentry/transport/AsyncConnection.java
index 678b3ddd5..853f83374 100644
--- a/sentry-core/src/main/java/io/sentry/core/transport/AsyncConnection.java
+++ b/sentry/src/main/java/io/sentry/transport/AsyncConnection.java
@@ -1,21 +1,17 @@
-package io.sentry.core.transport;
-
-import io.sentry.core.ILogger;
-import io.sentry.core.SentryEnvelope;
-import io.sentry.core.SentryEnvelopeItem;
-import io.sentry.core.SentryEvent;
-import io.sentry.core.SentryItemType;
-import io.sentry.core.SentryLevel;
-import io.sentry.core.SentryOptions;
-import io.sentry.core.cache.IEnvelopeCache;
-import io.sentry.core.cache.IEventCache;
-import io.sentry.core.hints.Cached;
-import io.sentry.core.hints.DiskFlushNotification;
-import io.sentry.core.hints.Retryable;
-import io.sentry.core.hints.SessionUpdate;
-import io.sentry.core.hints.SubmissionResult;
-import io.sentry.core.util.LogUtils;
-import io.sentry.core.util.Objects;
+package io.sentry.transport;
+
+import io.sentry.ILogger;
+import io.sentry.SentryEnvelope;
+import io.sentry.SentryEnvelopeItem;
+import io.sentry.SentryLevel;
+import io.sentry.SentryOptions;
+import io.sentry.cache.IEnvelopeCache;
+import io.sentry.hints.Cached;
+import io.sentry.hints.DiskFlushNotification;
+import io.sentry.hints.Retryable;
+import io.sentry.hints.SubmissionResult;
+import io.sentry.util.LogUtils;
+import io.sentry.util.Objects;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
@@ -35,23 +31,20 @@ public final class AsyncConnection implements Closeable, Connection {
private final @NotNull ITransport transport;
private final @NotNull ITransportGate transportGate;
private final @NotNull ExecutorService executor;
- private final @NotNull IEventCache eventCache;
- private final @NotNull IEnvelopeCache sessionCache;
+ private final @NotNull IEnvelopeCache envelopeCache;
private final @NotNull SentryOptions options;
public AsyncConnection(
final ITransport transport,
final ITransportGate transportGate,
- final IEventCache eventCache,
- final IEnvelopeCache sessionCache,
+ final IEnvelopeCache envelopeCache,
final int maxQueueSize,
final SentryOptions options) {
this(
transport,
transportGate,
- eventCache,
- sessionCache,
- initExecutor(maxQueueSize, eventCache, sessionCache, options.getLogger()),
+ envelopeCache,
+ initExecutor(maxQueueSize, envelopeCache, options.getLogger()),
options);
}
@@ -59,44 +52,31 @@ public AsyncConnection(
AsyncConnection(
final @NotNull ITransport transport,
final @NotNull ITransportGate transportGate,
- final @NotNull IEventCache eventCache,
- final @NotNull IEnvelopeCache sessionCache,
+ final @NotNull IEnvelopeCache envelopeCache,
final @NotNull ExecutorService executorService,
final @NotNull SentryOptions options) {
this.transport = transport;
this.transportGate = transportGate;
- this.eventCache = eventCache;
- this.sessionCache = sessionCache;
+ this.envelopeCache = envelopeCache;
this.options = options;
this.executor = executorService;
}
private static QueuedThreadPoolExecutor initExecutor(
final int maxQueueSize,
- final @NotNull IEventCache eventCache,
- final @NotNull IEnvelopeCache sessionCache,
+ final @NotNull IEnvelopeCache envelopeCache,
final @NotNull ILogger logger) {
final RejectedExecutionHandler storeEvents =
(r, executor) -> {
- if (r instanceof EventSender) {
- final EventSender eventSender = (EventSender) r;
-
- if (!(eventSender.hint instanceof Cached)) {
- eventCache.store(eventSender.event);
- }
+ if (r instanceof EnvelopeSender) {
+ final EnvelopeSender envelopeSender = (EnvelopeSender) r;
- markHintWhenSendingFailed(eventSender.hint, true);
- logger.log(SentryLevel.WARNING, "Event rejected: %s", eventSender.event.getEventId());
- }
- if (r instanceof SessionSender) {
- final SessionSender sessionSender = (SessionSender) r;
-
- if (!(sessionSender.hint instanceof Cached)) {
- sessionCache.store(sessionSender.envelope, sessionSender.hint);
+ if (!(envelopeSender.hint instanceof Cached)) {
+ envelopeCache.store(envelopeSender.envelope, envelopeSender.hint);
}
- markHintWhenSendingFailed(sessionSender.hint, true);
+ markHintWhenSendingFailed(envelopeSender.hint, true);
logger.log(SentryLevel.WARNING, "Envelope rejected");
}
};
@@ -120,42 +100,12 @@ private static void markHintWhenSendingFailed(final @Nullable Object hint, final
}
}
- /**
- * Tries to send the event to the Sentry server.
- *
- * @param event the event to send
- * @throws IOException on error
- */
- @SuppressWarnings("FutureReturnValueIgnored")
- @Override
- public void send(final @NotNull SentryEvent event, final @Nullable Object hint)
- throws IOException {
- IEventCache currentEventCache = eventCache;
- boolean cached = false;
- if (hint instanceof Cached) {
- currentEventCache = NoOpEventCache.getInstance();
- cached = true;
- options.getLogger().log(SentryLevel.DEBUG, "Captured SentryEvent is already cached");
- }
-
- // no reason to continue
- if (transport.isRetryAfter(SentryItemType.Event.getItemType())) {
- if (cached) {
- eventCache.discard(event);
- }
- markHintWhenSendingFailed(hint, false);
- return;
- }
-
- executor.submit(new EventSender(event, hint, currentEventCache));
- }
-
@SuppressWarnings("FutureReturnValueIgnored")
@Override
public void send(@NotNull SentryEnvelope envelope, final @Nullable Object hint)
throws IOException {
// For now no caching on envelopes
- IEnvelopeCache currentEnvelopeCache = sessionCache;
+ IEnvelopeCache currentEnvelopeCache = envelopeCache;
boolean cached = false;
if (hint instanceof Cached) {
currentEnvelopeCache = NoOpEnvelopeCache.getInstance();
@@ -192,7 +142,7 @@ public void send(@NotNull SentryEnvelope envelope, final @Nullable Object hint)
// no reason to continue
if (toSend.isEmpty()) {
if (cached) {
- sessionCache.discard(envelope);
+ envelopeCache.discard(envelope);
}
options.getLogger().log(SentryLevel.INFO, "Envelope discarded due all items rate limited.");
@@ -203,7 +153,7 @@ public void send(@NotNull SentryEnvelope envelope, final @Nullable Object hint)
envelope = new SentryEnvelope(envelope.getHeader(), toSend);
}
- executor.submit(new SessionSender(envelope, hint, currentEnvelopeCache));
+ executor.submit(new EnvelopeSender(envelope, hint, currentEnvelopeCache));
}
@Override
@@ -240,100 +190,19 @@ private static final class AsyncConnectionThreadFactory implements ThreadFactory
}
}
- private final class EventSender implements Runnable {
- private final SentryEvent event;
- private final Object hint;
- private final IEventCache eventCache;
- private final TransportResult failedResult = TransportResult.error(-1);
-
- EventSender(
- final @NotNull SentryEvent event,
- final @Nullable Object hint,
- final @NotNull IEventCache eventCache) {
- this.event = event;
- this.hint = hint;
- this.eventCache = eventCache;
- }
-
- @Override
- public void run() {
- TransportResult result = this.failedResult;
- try {
- result = flush();
- } catch (Exception e) {
- options
- .getLogger()
- .log(SentryLevel.ERROR, e, "Event submission failed: %s", event.getEventId());
- throw e;
- } finally {
- if (hint instanceof SubmissionResult) {
- options
- .getLogger()
- .log(SentryLevel.DEBUG, "Marking event submission result: %s", result.isSuccess());
- ((SubmissionResult) hint).setResult(result.isSuccess());
- }
- }
- }
-
- private @NotNull TransportResult flush() {
- TransportResult result = this.failedResult;
- eventCache.store(event);
-
- if (hint instanceof DiskFlushNotification) {
- ((DiskFlushNotification) hint).markFlushed();
- options
- .getLogger()
- .log(SentryLevel.DEBUG, "Disk flush event fired: %s", event.getEventId());
- }
-
- if (transportGate.isConnected()) {
- try {
- result = transport.send(event);
- if (result.isSuccess()) {
- eventCache.discard(event);
- } else {
- final String message =
- "The transport failed to send the event with response code "
- + result.getResponseCode();
-
- options.getLogger().log(SentryLevel.ERROR, message);
-
- throw new IllegalStateException(message);
- }
- } catch (IOException e) {
- // Failure due to IO is allowed to retry the event
- if (hint instanceof Retryable) {
- ((Retryable) hint).setRetry(true);
- } else {
- LogUtils.logIfNotRetryable(options.getLogger(), hint);
- }
- throw new IllegalStateException("Sending the event failed.", e);
- }
- } else {
- // If transportGate is blocking from sending, allowed to retry
- if (hint instanceof Retryable) {
- ((Retryable) hint).setRetry(true);
- } else {
- LogUtils.logIfNotRetryable(options.getLogger(), hint);
- }
- }
- return result;
- }
- }
-
- private final class SessionSender implements Runnable {
+ private final class EnvelopeSender implements Runnable {
private final @NotNull SentryEnvelope envelope;
private final @Nullable Object hint;
- private final @NotNull IEnvelopeCache sessionCache;
- private final TransportResult failedResult = TransportResult.error(-1);
+ private final @NotNull IEnvelopeCache envelopeCache;
+ private final TransportResult failedResult = TransportResult.error();
- SessionSender(
+ EnvelopeSender(
final @NotNull SentryEnvelope envelope,
final @Nullable Object hint,
- final @NotNull IEnvelopeCache sessionCache) {
+ final @NotNull IEnvelopeCache envelopeCache) {
this.envelope = Objects.requireNonNull(envelope, "Envelope is required.");
this.hint = hint;
- this.sessionCache = Objects.requireNonNull(sessionCache, "SessionCache is required.");
+ this.envelopeCache = Objects.requireNonNull(envelopeCache, "EnvelopeCache is required.");
}
@Override
@@ -358,21 +227,18 @@ public void run() {
private @NotNull TransportResult flush() {
TransportResult result = this.failedResult;
- sessionCache.store(envelope, hint);
+ envelopeCache.store(envelope, hint);
- // we only flush a session update to the disk, but not to the network
- if (hint instanceof SessionUpdate) {
- options
- .getLogger()
- .log(SentryLevel.DEBUG, "SessionUpdate event, leaving after event being cached.");
- return TransportResult.success();
+ if (hint instanceof DiskFlushNotification) {
+ ((DiskFlushNotification) hint).markFlushed();
+ options.getLogger().log(SentryLevel.DEBUG, "Disk flush envelope fired");
}
if (transportGate.isConnected()) {
try {
result = transport.send(envelope);
if (result.isSuccess()) {
- sessionCache.discard(envelope);
+ envelopeCache.discard(envelope);
} else {
final String message =
"The transport failed to send the envelope with response code "
diff --git a/sentry-core/src/main/java/io/sentry/core/transport/Connection.java b/sentry/src/main/java/io/sentry/transport/Connection.java
similarity index 53%
rename from sentry-core/src/main/java/io/sentry/core/transport/Connection.java
rename to sentry/src/main/java/io/sentry/transport/Connection.java
index 02a5da2ae..9aa8a39db 100644
--- a/sentry-core/src/main/java/io/sentry/core/transport/Connection.java
+++ b/sentry/src/main/java/io/sentry/transport/Connection.java
@@ -1,17 +1,10 @@
-package io.sentry.core.transport;
+package io.sentry.transport;
-import io.sentry.core.SentryEnvelope;
-import io.sentry.core.SentryEvent;
+import io.sentry.SentryEnvelope;
import java.io.IOException;
import org.jetbrains.annotations.Nullable;
public interface Connection {
- void send(SentryEvent event, @Nullable Object hint) throws IOException;
-
- default void send(SentryEvent event) throws IOException {
- send(event, null);
- }
-
void send(SentryEnvelope event, @Nullable Object hint) throws IOException;
default void send(SentryEnvelope envelope) throws IOException {
diff --git a/sentry-core/src/main/java/io/sentry/core/transport/CurrentDateProvider.java b/sentry/src/main/java/io/sentry/transport/CurrentDateProvider.java
similarity index 92%
rename from sentry-core/src/main/java/io/sentry/core/transport/CurrentDateProvider.java
rename to sentry/src/main/java/io/sentry/transport/CurrentDateProvider.java
index 289b6e048..9421baac5 100644
--- a/sentry-core/src/main/java/io/sentry/core/transport/CurrentDateProvider.java
+++ b/sentry/src/main/java/io/sentry/transport/CurrentDateProvider.java
@@ -1,4 +1,4 @@
-package io.sentry.core.transport;
+package io.sentry.transport;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/transport/HttpTransport.java b/sentry/src/main/java/io/sentry/transport/HttpTransport.java
similarity index 85%
rename from sentry-core/src/main/java/io/sentry/core/transport/HttpTransport.java
rename to sentry/src/main/java/io/sentry/transport/HttpTransport.java
index bf5f835db..e8b0b410c 100644
--- a/sentry-core/src/main/java/io/sentry/core/transport/HttpTransport.java
+++ b/sentry/src/main/java/io/sentry/transport/HttpTransport.java
@@ -1,18 +1,17 @@
-package io.sentry.core.transport;
+package io.sentry.transport;
-import static io.sentry.core.SentryLevel.DEBUG;
-import static io.sentry.core.SentryLevel.ERROR;
-import static io.sentry.core.SentryLevel.INFO;
+import static io.sentry.SentryLevel.DEBUG;
+import static io.sentry.SentryLevel.ERROR;
+import static io.sentry.SentryLevel.INFO;
import static java.net.HttpURLConnection.HTTP_OK;
import com.jakewharton.nopen.annotation.Open;
-import io.sentry.core.ILogger;
-import io.sentry.core.ISerializer;
-import io.sentry.core.SentryEnvelope;
-import io.sentry.core.SentryEvent;
-import io.sentry.core.SentryOptions;
-import io.sentry.core.util.Objects;
-import io.sentry.core.util.StringUtils;
+import io.sentry.ILogger;
+import io.sentry.ISerializer;
+import io.sentry.SentryEnvelope;
+import io.sentry.SentryOptions;
+import io.sentry.util.Objects;
+import io.sentry.util.StringUtils;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
@@ -76,7 +75,6 @@ public String getCategory() {
private final int connectionTimeout;
private final int readTimeout;
private final boolean bypassSecurity;
- private final @NotNull URL storeUrl;
private final @NotNull URL envelopeUrl;
private final @NotNull SentryOptions options;
@@ -90,8 +88,8 @@ public String getCategory() {
/**
* Constructs a new HTTP transport instance. Notably, the provided {@code requestUpdater} must set
- * the appropriate content encoding header for the {@link io.sentry.core.ISerializer} instance
- * obtained from the options.
+ * the appropriate content encoding header for the {@link io.sentry.ISerializer} instance obtained
+ * from the options.
*
* @param options sentry options to read the config from
* @param connectionConfigurator this consumer is given a chance to set up the request before it
@@ -139,42 +137,15 @@ public HttpTransport(
try {
final URI uri = sentryUrl.toURI();
- storeUrl = uri.resolve(uri.getPath() + "/store/").toURL();
envelopeUrl = uri.resolve(uri.getPath() + "/envelope/").toURL();
} catch (URISyntaxException | MalformedURLException e) {
throw new IllegalArgumentException("Failed to compose the Sentry's server URL.", e);
}
}
- // giving up on testing this method is probably the simplest way of having the rest of the class
- // testable...
- protected @NotNull HttpURLConnection open(final @Nullable Proxy proxy) throws IOException {
- return open(storeUrl, proxy);
- }
-
- protected @NotNull HttpURLConnection open(final @NotNull URL url, final @Nullable Proxy proxy)
- throws IOException {
- return (HttpURLConnection) (proxy == null ? url.openConnection() : url.openConnection(proxy));
- }
-
- @Override
- public @NotNull TransportResult send(final @NotNull SentryEvent event) throws IOException {
- final HttpURLConnection connection = createConnection(false);
- TransportResult result;
-
- try (final OutputStream outputStream = connection.getOutputStream();
- final GZIPOutputStream gzip = new GZIPOutputStream(outputStream);
- final Writer writer = new BufferedWriter(new OutputStreamWriter(gzip, UTF_8))) {
-
- serializer.serialize(event, writer);
- } catch (IOException e) {
- logger.log(
- ERROR, e, "An exception occurred while submitting the event to the Sentry server.");
- } finally {
- result =
- readAndLog(connection, String.format("Event sent %s successfully.", event.getEventId()));
- }
- return result;
+ protected @NotNull HttpURLConnection open() throws IOException {
+ return (HttpURLConnection)
+ (proxy == null ? envelopeUrl.openConnection() : envelopeUrl.openConnection(proxy));
}
/**
@@ -235,19 +206,11 @@ public boolean isRetryAfter(final @NotNull String itemType) {
/**
* Create a HttpURLConnection connection Sets specific content-type if its an envelope or not
*
- * @param asEnvelope if its an envelope or not
* @return the HttpURLConnection
* @throws IOException if connection has a problem
*/
- private @NotNull HttpURLConnection createConnection(boolean asEnvelope) throws IOException {
- String contentType = "application/json";
- HttpURLConnection connection;
- if (asEnvelope) {
- connection = open(envelopeUrl, proxy);
- contentType = "application/x-sentry-envelope";
- } else {
- connection = open(proxy);
- }
+ private @NotNull HttpURLConnection createConnection() throws IOException {
+ HttpURLConnection connection = open();
connectionConfigurator.configure(connection);
connection.setRequestMethod("POST");
@@ -255,7 +218,7 @@ public boolean isRetryAfter(final @NotNull String itemType) {
connection.setChunkedStreamingMode(0);
connection.setRequestProperty("Content-Encoding", "gzip");
- connection.setRequestProperty("Content-Type", contentType);
+ connection.setRequestProperty("Content-Type", "application/x-sentry-envelope");
connection.setRequestProperty("Accept", "application/json");
// https://stackoverflow.com/questions/52726909/java-io-ioexception-unexpected-end-of-stream-on-connection/53089882
@@ -274,7 +237,7 @@ public boolean isRetryAfter(final @NotNull String itemType) {
@Override
public @NotNull TransportResult send(final @NotNull SentryEnvelope envelope) throws IOException {
- final HttpURLConnection connection = createConnection(true);
+ final HttpURLConnection connection = createConnection();
TransportResult result;
try (final OutputStream outputStream = connection.getOutputStream();
@@ -286,7 +249,7 @@ public boolean isRetryAfter(final @NotNull String itemType) {
logger.log(
ERROR, e, "An exception occurred while submitting the envelope to the Sentry server.");
} finally {
- result = readAndLog(connection, "Envelope sent successfully.");
+ result = readAndLog(connection);
}
return result;
}
@@ -295,11 +258,9 @@ public boolean isRetryAfter(final @NotNull String itemType) {
* Read responde code, retry after header and its error stream if there are errors and log it
*
* @param connection the HttpURLConnection
- * @param message the message, if custom message if its an event or envelope
* @return TransportResult.success if responseCode is 200 or TransportResult.error otherwise
*/
- private @NotNull TransportResult readAndLog(
- final @NotNull HttpURLConnection connection, final @NotNull String message) {
+ private @NotNull TransportResult readAndLog(final @NotNull HttpURLConnection connection) {
try {
final int responseCode = connection.getResponseCode();
@@ -316,7 +277,7 @@ public boolean isRetryAfter(final @NotNull String itemType) {
return TransportResult.error(responseCode);
}
- logger.log(DEBUG, message);
+ logger.log(DEBUG, "Envelope sent successfully.");
return TransportResult.success();
} catch (IOException e) {
diff --git a/sentry-core/src/main/java/io/sentry/core/transport/IConnectionConfigurator.java b/sentry/src/main/java/io/sentry/transport/IConnectionConfigurator.java
similarity index 90%
rename from sentry-core/src/main/java/io/sentry/core/transport/IConnectionConfigurator.java
rename to sentry/src/main/java/io/sentry/transport/IConnectionConfigurator.java
index a067095ed..11337926b 100644
--- a/sentry-core/src/main/java/io/sentry/core/transport/IConnectionConfigurator.java
+++ b/sentry/src/main/java/io/sentry/transport/IConnectionConfigurator.java
@@ -1,4 +1,4 @@
-package io.sentry.core.transport;
+package io.sentry.transport;
import java.net.HttpURLConnection;
diff --git a/sentry-core/src/main/java/io/sentry/core/transport/ICurrentDateProvider.java b/sentry/src/main/java/io/sentry/transport/ICurrentDateProvider.java
similarity index 89%
rename from sentry-core/src/main/java/io/sentry/core/transport/ICurrentDateProvider.java
rename to sentry/src/main/java/io/sentry/transport/ICurrentDateProvider.java
index a4f681002..b4f86cf2d 100644
--- a/sentry-core/src/main/java/io/sentry/core/transport/ICurrentDateProvider.java
+++ b/sentry/src/main/java/io/sentry/transport/ICurrentDateProvider.java
@@ -1,4 +1,4 @@
-package io.sentry.core.transport;
+package io.sentry.transport;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/transport/ITransport.java b/sentry/src/main/java/io/sentry/transport/ITransport.java
similarity index 62%
rename from sentry-core/src/main/java/io/sentry/core/transport/ITransport.java
rename to sentry/src/main/java/io/sentry/transport/ITransport.java
index a2c424521..6ce301b5f 100644
--- a/sentry-core/src/main/java/io/sentry/core/transport/ITransport.java
+++ b/sentry/src/main/java/io/sentry/transport/ITransport.java
@@ -1,14 +1,11 @@
-package io.sentry.core.transport;
+package io.sentry.transport;
-import io.sentry.core.SentryEnvelope;
-import io.sentry.core.SentryEvent;
+import io.sentry.SentryEnvelope;
import java.io.Closeable;
import java.io.IOException;
/** A transport is in charge of sending the event to the Sentry server. */
public interface ITransport extends Closeable {
- TransportResult send(SentryEvent event) throws IOException;
-
boolean isRetryAfter(String type);
TransportResult send(SentryEnvelope envelope) throws IOException;
diff --git a/sentry-core/src/main/java/io/sentry/core/transport/ITransportGate.java b/sentry/src/main/java/io/sentry/transport/ITransportGate.java
similarity index 94%
rename from sentry-core/src/main/java/io/sentry/core/transport/ITransportGate.java
rename to sentry/src/main/java/io/sentry/transport/ITransportGate.java
index 2493a8969..2a26658c9 100644
--- a/sentry-core/src/main/java/io/sentry/core/transport/ITransportGate.java
+++ b/sentry/src/main/java/io/sentry/transport/ITransportGate.java
@@ -1,4 +1,4 @@
-package io.sentry.core.transport;
+package io.sentry.transport;
/**
* Implementations of this interface serve as gatekeepers that allow or disallow sending of the
diff --git a/sentry-core/src/main/java/io/sentry/core/transport/NoOpEnvelopeCache.java b/sentry/src/main/java/io/sentry/transport/NoOpEnvelopeCache.java
similarity index 84%
rename from sentry-core/src/main/java/io/sentry/core/transport/NoOpEnvelopeCache.java
rename to sentry/src/main/java/io/sentry/transport/NoOpEnvelopeCache.java
index 82b63959c..712623b17 100644
--- a/sentry-core/src/main/java/io/sentry/core/transport/NoOpEnvelopeCache.java
+++ b/sentry/src/main/java/io/sentry/transport/NoOpEnvelopeCache.java
@@ -1,7 +1,7 @@
-package io.sentry.core.transport;
+package io.sentry.transport;
-import io.sentry.core.SentryEnvelope;
-import io.sentry.core.cache.IEnvelopeCache;
+import io.sentry.SentryEnvelope;
+import io.sentry.cache.IEnvelopeCache;
import java.util.ArrayList;
import java.util.Iterator;
import org.jetbrains.annotations.NotNull;
diff --git a/sentry-core/src/main/java/io/sentry/core/transport/NoOpTransport.java b/sentry/src/main/java/io/sentry/transport/NoOpTransport.java
similarity index 71%
rename from sentry-core/src/main/java/io/sentry/core/transport/NoOpTransport.java
rename to sentry/src/main/java/io/sentry/transport/NoOpTransport.java
index c0de83a03..194ef1535 100644
--- a/sentry-core/src/main/java/io/sentry/core/transport/NoOpTransport.java
+++ b/sentry/src/main/java/io/sentry/transport/NoOpTransport.java
@@ -1,7 +1,6 @@
-package io.sentry.core.transport;
+package io.sentry.transport;
-import io.sentry.core.SentryEnvelope;
-import io.sentry.core.SentryEvent;
+import io.sentry.SentryEnvelope;
import java.io.IOException;
import org.jetbrains.annotations.ApiStatus;
@@ -16,11 +15,6 @@ public static NoOpTransport getInstance() {
private NoOpTransport() {}
- @Override
- public TransportResult send(SentryEvent event) throws IOException {
- return TransportResult.success();
- }
-
@Override
public boolean isRetryAfter(String type) {
return false;
diff --git a/sentry-core/src/main/java/io/sentry/core/transport/NoOpTransportGate.java b/sentry/src/main/java/io/sentry/transport/NoOpTransportGate.java
similarity index 90%
rename from sentry-core/src/main/java/io/sentry/core/transport/NoOpTransportGate.java
rename to sentry/src/main/java/io/sentry/transport/NoOpTransportGate.java
index e9992d275..6b14f5ae3 100644
--- a/sentry-core/src/main/java/io/sentry/core/transport/NoOpTransportGate.java
+++ b/sentry/src/main/java/io/sentry/transport/NoOpTransportGate.java
@@ -1,4 +1,4 @@
-package io.sentry.core.transport;
+package io.sentry.transport;
public final class NoOpTransportGate implements ITransportGate {
diff --git a/sentry-core/src/main/java/io/sentry/core/transport/QueuedThreadPoolExecutor.java b/sentry/src/main/java/io/sentry/transport/QueuedThreadPoolExecutor.java
similarity index 97%
rename from sentry-core/src/main/java/io/sentry/core/transport/QueuedThreadPoolExecutor.java
rename to sentry/src/main/java/io/sentry/transport/QueuedThreadPoolExecutor.java
index 9ed64167a..dda6a985c 100644
--- a/sentry-core/src/main/java/io/sentry/core/transport/QueuedThreadPoolExecutor.java
+++ b/sentry/src/main/java/io/sentry/transport/QueuedThreadPoolExecutor.java
@@ -1,7 +1,7 @@
-package io.sentry.core.transport;
+package io.sentry.transport;
-import io.sentry.core.ILogger;
-import io.sentry.core.SentryLevel;
+import io.sentry.ILogger;
+import io.sentry.SentryLevel;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
diff --git a/sentry-core/src/main/java/io/sentry/core/transport/StdoutTransport.java b/sentry/src/main/java/io/sentry/transport/StdoutTransport.java
similarity index 64%
rename from sentry-core/src/main/java/io/sentry/core/transport/StdoutTransport.java
rename to sentry/src/main/java/io/sentry/transport/StdoutTransport.java
index f82f4e079..058db0719 100644
--- a/sentry-core/src/main/java/io/sentry/core/transport/StdoutTransport.java
+++ b/sentry/src/main/java/io/sentry/transport/StdoutTransport.java
@@ -1,9 +1,8 @@
-package io.sentry.core.transport;
+package io.sentry.transport;
-import io.sentry.core.ISerializer;
-import io.sentry.core.SentryEnvelope;
-import io.sentry.core.SentryEvent;
-import io.sentry.core.util.Objects;
+import io.sentry.ISerializer;
+import io.sentry.SentryEnvelope;
+import io.sentry.util.Objects;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
@@ -22,17 +21,6 @@ public StdoutTransport(final @NotNull ISerializer serializer) {
this.serializer = Objects.requireNonNull(serializer, "Serializer is required");
}
- @Override
- public TransportResult send(final @NotNull SentryEvent event) throws IOException {
- Objects.requireNonNull(event, "SentryEvent is required");
-
- try (final Writer writer = new BufferedWriter(new OutputStreamWriter(System.out, UTF_8));
- final Writer printWriter = new PrintWriter(writer)) {
- serializer.serialize(event, printWriter);
- return TransportResult.success();
- }
- }
-
@Override
public boolean isRetryAfter(String type) {
return false;
@@ -47,7 +35,7 @@ public TransportResult send(final @NotNull SentryEnvelope envelope) throws IOExc
serializer.serialize(envelope, printWriter);
return TransportResult.success();
} catch (Exception e) {
- return TransportResult.error(-1);
+ return TransportResult.error();
}
}
diff --git a/sentry-core/src/main/java/io/sentry/core/transport/TransportResult.java b/sentry/src/main/java/io/sentry/transport/TransportResult.java
similarity index 86%
rename from sentry-core/src/main/java/io/sentry/core/transport/TransportResult.java
rename to sentry/src/main/java/io/sentry/transport/TransportResult.java
index 3ed1cf48e..301296f00 100644
--- a/sentry-core/src/main/java/io/sentry/core/transport/TransportResult.java
+++ b/sentry/src/main/java/io/sentry/transport/TransportResult.java
@@ -1,12 +1,11 @@
-package io.sentry.core.transport;
+package io.sentry.transport;
-import io.sentry.core.SentryEvent;
import org.jetbrains.annotations.NotNull;
/**
- * A result of {@link ITransport#send(SentryEvent)}. Note that this class is intentionally not
- * subclassable and has only two factory methods to capture the 2 possible states - success or
- * error.
+ * A result of {@link ITransport#send(io.sentry.SentryEnvelope)}. Note that this class is
+ * intentionally not subclassable and has only two factory methods to capture the 2 possible states
+ * - success or error.
*/
public abstract class TransportResult {
diff --git a/sentry-core/src/main/java/io/sentry/core/util/ApplyScopeUtils.java b/sentry/src/main/java/io/sentry/util/ApplyScopeUtils.java
similarity index 85%
rename from sentry-core/src/main/java/io/sentry/core/util/ApplyScopeUtils.java
rename to sentry/src/main/java/io/sentry/util/ApplyScopeUtils.java
index 002a6c124..420540ae0 100644
--- a/sentry-core/src/main/java/io/sentry/core/util/ApplyScopeUtils.java
+++ b/sentry/src/main/java/io/sentry/util/ApplyScopeUtils.java
@@ -1,7 +1,7 @@
-package io.sentry.core.util;
+package io.sentry.util;
-import io.sentry.core.hints.ApplyScopeData;
-import io.sentry.core.hints.Cached;
+import io.sentry.hints.ApplyScopeData;
+import io.sentry.hints.Cached;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
diff --git a/sentry-core/src/main/java/io/sentry/core/util/CollectionUtils.java b/sentry/src/main/java/io/sentry/util/CollectionUtils.java
similarity index 97%
rename from sentry-core/src/main/java/io/sentry/core/util/CollectionUtils.java
rename to sentry/src/main/java/io/sentry/util/CollectionUtils.java
index 9fc6b95a5..b4f29dc6c 100644
--- a/sentry-core/src/main/java/io/sentry/core/util/CollectionUtils.java
+++ b/sentry/src/main/java/io/sentry/util/CollectionUtils.java
@@ -1,4 +1,4 @@
-package io.sentry.core.util;
+package io.sentry.util;
import java.util.Collection;
import java.util.Map;
diff --git a/sentry-core/src/main/java/io/sentry/core/util/LogUtils.java b/sentry/src/main/java/io/sentry/util/LogUtils.java
similarity index 87%
rename from sentry-core/src/main/java/io/sentry/core/util/LogUtils.java
rename to sentry/src/main/java/io/sentry/util/LogUtils.java
index 717802e7b..6e5ba9a61 100644
--- a/sentry-core/src/main/java/io/sentry/core/util/LogUtils.java
+++ b/sentry/src/main/java/io/sentry/util/LogUtils.java
@@ -1,7 +1,7 @@
-package io.sentry.core.util;
+package io.sentry.util;
-import io.sentry.core.ILogger;
-import io.sentry.core.SentryLevel;
+import io.sentry.ILogger;
+import io.sentry.SentryLevel;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
diff --git a/sentry-core/src/main/java/io/sentry/core/util/Objects.java b/sentry/src/main/java/io/sentry/util/Objects.java
similarity index 90%
rename from sentry-core/src/main/java/io/sentry/core/util/Objects.java
rename to sentry/src/main/java/io/sentry/util/Objects.java
index aed1623cc..f39fa2c58 100644
--- a/sentry-core/src/main/java/io/sentry/core/util/Objects.java
+++ b/sentry/src/main/java/io/sentry/util/Objects.java
@@ -1,4 +1,4 @@
-package io.sentry.core.util;
+package io.sentry.util;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/main/java/io/sentry/core/util/StringUtils.java b/sentry/src/main/java/io/sentry/util/StringUtils.java
similarity index 97%
rename from sentry-core/src/main/java/io/sentry/core/util/StringUtils.java
rename to sentry/src/main/java/io/sentry/util/StringUtils.java
index 652f3cb03..05b938baf 100644
--- a/sentry-core/src/main/java/io/sentry/core/util/StringUtils.java
+++ b/sentry/src/main/java/io/sentry/util/StringUtils.java
@@ -1,4 +1,4 @@
-package io.sentry.core.util;
+package io.sentry.util;
import java.util.Locale;
import org.jetbrains.annotations.ApiStatus;
diff --git a/sentry-core/src/test/java/io/sentry/core/BreadcrumbTest.kt b/sentry/src/test/java/io/sentry/BreadcrumbTest.kt
similarity index 90%
rename from sentry-core/src/test/java/io/sentry/core/BreadcrumbTest.kt
rename to sentry/src/test/java/io/sentry/BreadcrumbTest.kt
index d38ef8966..a1971bb72 100644
--- a/sentry-core/src/test/java/io/sentry/core/BreadcrumbTest.kt
+++ b/sentry/src/test/java/io/sentry/BreadcrumbTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -98,4 +98,13 @@ class BreadcrumbTest {
val breadcrumb = Breadcrumb("this is a test")
assertEquals("this is a test", breadcrumb.message)
}
+
+ @Test
+ fun `creates HTTP breadcrumb`() {
+ val breadcrumb = Breadcrumb.http("http://example.com", "POST")
+ assertEquals("http://example.com", breadcrumb.data["url"])
+ assertEquals("POST", breadcrumb.data["method"])
+ assertEquals("http", breadcrumb.type)
+ assertEquals("http", breadcrumb.category)
+ }
}
diff --git a/sentry/src/test/java/io/sentry/CachedEvent.kt b/sentry/src/test/java/io/sentry/CachedEvent.kt
new file mode 100644
index 000000000..5afdbe5d6
--- /dev/null
+++ b/sentry/src/test/java/io/sentry/CachedEvent.kt
@@ -0,0 +1,5 @@
+package io.sentry
+
+import io.sentry.hints.Cached
+
+class CachedEvent : Cached
diff --git a/sentry-core/src/test/java/io/sentry/core/CustomEventProcessor.kt b/sentry/src/test/java/io/sentry/CustomEventProcessor.kt
similarity index 84%
rename from sentry-core/src/test/java/io/sentry/core/CustomEventProcessor.kt
rename to sentry/src/test/java/io/sentry/CustomEventProcessor.kt
index 57dbf2acb..5845c6286 100644
--- a/sentry-core/src/test/java/io/sentry/core/CustomEventProcessor.kt
+++ b/sentry/src/test/java/io/sentry/CustomEventProcessor.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
class CustomEventProcessor : EventProcessor {
override fun process(event: SentryEvent, hint: Any?): SentryEvent? = null
diff --git a/sentry-core/src/test/java/io/sentry/core/DateUtilsTest.kt b/sentry/src/test/java/io/sentry/DateUtilsTest.kt
similarity index 96%
rename from sentry-core/src/test/java/io/sentry/core/DateUtilsTest.kt
rename to sentry/src/test/java/io/sentry/DateUtilsTest.kt
index a4694e9ff..eeedb7e57 100644
--- a/sentry-core/src/test/java/io/sentry/core/DateUtilsTest.kt
+++ b/sentry/src/test/java/io/sentry/DateUtilsTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git a/sentry-core/src/test/java/io/sentry/core/DiagnosticLoggerTest.kt b/sentry/src/test/java/io/sentry/DiagnosticLoggerTest.kt
similarity index 99%
rename from sentry-core/src/test/java/io/sentry/core/DiagnosticLoggerTest.kt
rename to sentry/src/test/java/io/sentry/DiagnosticLoggerTest.kt
index 110a5e23d..c8af2a636 100644
--- a/sentry-core/src/test/java/io/sentry/core/DiagnosticLoggerTest.kt
+++ b/sentry/src/test/java/io/sentry/DiagnosticLoggerTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.mock
diff --git a/sentry-core/src/test/java/io/sentry/core/DirectoryProcessorTest.kt b/sentry/src/test/java/io/sentry/DirectoryProcessorTest.kt
similarity index 74%
rename from sentry-core/src/test/java/io/sentry/core/DirectoryProcessorTest.kt
rename to sentry/src/test/java/io/sentry/DirectoryProcessorTest.kt
index 7a8e0cea2..2c130de02 100644
--- a/sentry-core/src/test/java/io/sentry/core/DirectoryProcessorTest.kt
+++ b/sentry/src/test/java/io/sentry/DirectoryProcessorTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.argWhere
@@ -6,9 +6,9 @@ import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
-import io.sentry.core.hints.ApplyScopeData
-import io.sentry.core.protocol.User
-import io.sentry.core.util.noFlushTimeout
+import io.sentry.hints.ApplyScopeData
+import io.sentry.protocol.User
+import io.sentry.util.noFlushTimeout
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
@@ -32,8 +32,8 @@ class DirectoryProcessorTest {
options.setLogger(logger)
}
- fun getSut(): EnvelopeSender {
- return EnvelopeSender(hub, envelopeReader, serializer, logger, 15000)
+ fun getSut(): OutboxSender {
+ return OutboxSender(hub, envelopeReader, serializer, logger, 15000)
}
}
@@ -55,11 +55,17 @@ class DirectoryProcessorTest {
fun `process directory folder has a non ApplyScopeData hint`() {
val path = getTempEnvelope("envelope-event-attachment.txt")
assertTrue(File(path).exists()) // sanity check
- val session = createSession()
- whenever(fixture.envelopeReader.read(any())).thenReturn(SentryEnvelope.fromSession(fixture.serializer, session, null))
- whenever(fixture.serializer.deserializeSession(any())).thenReturn(session)
+// val session = createSession()
+// whenever(fixture.envelopeReader.read(any())).thenReturn(SentryEnvelope.fromSession(fixture.serializer, session, null))
+// whenever(fixture.serializer.deserializeSession(any())).thenReturn(session)
+ val event = SentryEvent()
+ val envelope = SentryEnvelope.fromEvent(fixture.serializer, event, null)
+
+ whenever(fixture.envelopeReader.read(any())).thenReturn(envelope)
+ whenever(fixture.serializer.deserializeEvent(any())).thenReturn(event)
+
fixture.getSut().processDirectory(file)
- verify(fixture.hub).captureEnvelope(any(), argWhere { it !is ApplyScopeData })
+ verify(fixture.hub).captureEvent(any(), argWhere { it !is ApplyScopeData })
}
@Test
diff --git a/sentry-core/src/test/java/io/sentry/core/DsnTest.kt b/sentry/src/test/java/io/sentry/DsnTest.kt
similarity index 99%
rename from sentry-core/src/test/java/io/sentry/core/DsnTest.kt
rename to sentry/src/test/java/io/sentry/DsnTest.kt
index bdc2ebe6c..0d1e9c314 100644
--- a/sentry-core/src/test/java/io/sentry/core/DsnTest.kt
+++ b/sentry/src/test/java/io/sentry/DsnTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git a/sentry/src/test/java/io/sentry/DuplicateEventDetectionEventProcessorTest.kt b/sentry/src/test/java/io/sentry/DuplicateEventDetectionEventProcessorTest.kt
new file mode 100644
index 000000000..b2e7ec0ef
--- /dev/null
+++ b/sentry/src/test/java/io/sentry/DuplicateEventDetectionEventProcessorTest.kt
@@ -0,0 +1,70 @@
+package io.sentry
+
+import io.sentry.exception.ExceptionMechanismException
+import io.sentry.protocol.Mechanism
+import java.lang.RuntimeException
+import kotlin.test.Test
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+
+class DuplicateEventDetectionEventProcessorTest {
+
+ val processor = DuplicateEventDetectionEventProcessor(SentryOptions())
+
+ @Test
+ fun `does not drop event if no previous event with same exception was processed`() {
+ processor.process(SentryEvent(), null)
+
+ val result = processor.process(SentryEvent(RuntimeException()), null)
+
+ assertNotNull(result)
+ }
+
+ @Test
+ fun `drops event with the same exception`() {
+ val event = SentryEvent(RuntimeException())
+ processor.process(event, null)
+
+ val result = processor.process(event, null)
+ assertNull(result)
+ }
+
+ @Test
+ fun `drops event with mechanism exception having an exception that has already been processed`() {
+ val event = SentryEvent(RuntimeException())
+ processor.process(event, null)
+
+ val result = processor.process(SentryEvent(ExceptionMechanismException(Mechanism(), event.throwable, null)), null)
+ assertNull(result)
+ }
+
+ @Test
+ fun `drops event with exception that has already been processed with event with mechanism exception`() {
+ val sentryEvent = SentryEvent(ExceptionMechanismException(Mechanism(), RuntimeException(), null))
+ processor.process(sentryEvent, null)
+
+ val result = processor.process(SentryEvent((sentryEvent.throwable as ExceptionMechanismException).throwable), null)
+
+ assertNull(result)
+ }
+
+ @Test
+ fun `drops event with the cause equal to exception in already processed event`() {
+ val event = SentryEvent(RuntimeException())
+ processor.process(event, null)
+
+ val result = processor.process(SentryEvent(RuntimeException(event.throwable)), null)
+
+ assertNull(result)
+ }
+
+ @Test
+ fun `drops event with any of the causes has been already processed`() {
+ val event = SentryEvent(RuntimeException())
+ processor.process(event, null)
+
+ val result = processor.process(SentryEvent(RuntimeException(RuntimeException(event.throwable))), null)
+
+ assertNull(result)
+ }
+}
diff --git a/sentry-core/src/test/java/io/sentry/core/SendCachedEventTest.kt b/sentry/src/test/java/io/sentry/EnvelopeSenderTest.kt
similarity index 75%
rename from sentry-core/src/test/java/io/sentry/core/SendCachedEventTest.kt
rename to sentry/src/test/java/io/sentry/EnvelopeSenderTest.kt
index c32449807..595a2cf98 100644
--- a/sentry-core/src/test/java/io/sentry/core/SendCachedEventTest.kt
+++ b/sentry/src/test/java/io/sentry/EnvelopeSenderTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.doThrow
@@ -7,9 +7,9 @@ import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import com.nhaarman.mockitokotlin2.whenever
-import io.sentry.core.cache.DiskCache
-import io.sentry.core.hints.Retryable
-import io.sentry.core.util.noFlushTimeout
+import io.sentry.cache.EnvelopeCache
+import io.sentry.hints.Retryable
+import io.sentry.util.noFlushTimeout
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
@@ -18,7 +18,7 @@ import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertFalse
-class SendCachedEventTest {
+class EnvelopeSenderTest {
private class Fixture {
var hub: IHub? = mock()
var logger: ILogger? = mock()
@@ -30,8 +30,8 @@ class SendCachedEventTest {
options.setLogger(logger)
}
- fun getSut(): SendCachedEvent {
- return SendCachedEvent(serializer!!, hub!!, logger!!, options.flushTimeoutMillis)
+ fun getSut(): EnvelopeSender {
+ return EnvelopeSender(hub!!, serializer!!, logger!!, options.flushTimeoutMillis)
}
}
@@ -59,7 +59,7 @@ class SendCachedEventTest {
@Test
fun `when directory is actually a file, processDirectory logs and returns`() {
val sut = fixture.getSut()
- val testFile = File(Files.createTempFile("send-cached-event-test", DiskCache.FILE_SUFFIX).toUri())
+ val testFile = File(Files.createTempFile("send-cached-event-test", EnvelopeCache.SUFFIX_ENVELOPE_FILE).toUri())
testFile.deleteOnExit()
sut.processDirectory(testFile)
verify(fixture.logger)!!.log(eq(SentryLevel.ERROR), eq("Cache dir %s is not a directory."), any())
@@ -78,38 +78,38 @@ class SendCachedEventTest {
@Test
fun `when directory has event files, processDirectory captures with hub`() {
- val expected = SentryEvent()
- whenever(fixture.serializer!!.deserializeEvent(any())).thenReturn(expected)
+ val event = SentryEvent()
+ val envelope = SentryEnvelope.fromEvent(fixture.serializer!!, event, null)
+ whenever(fixture.serializer!!.deserializeEnvelope(any())).thenReturn(envelope)
val sut = fixture.getSut()
- val testFile = File(Files.createTempFile(tempDirectory, "send-cached-event-test", DiskCache.FILE_SUFFIX).toUri())
+ val testFile = File(Files.createTempFile(tempDirectory, "send-cached-event-test", EnvelopeCache.SUFFIX_ENVELOPE_FILE).toUri())
testFile.deleteOnExit()
sut.processDirectory(File(tempDirectory.toUri()))
- verify(fixture.hub)!!.captureEvent(eq(expected), any())
+ verify(fixture.hub)!!.captureEnvelope(eq(envelope), any())
}
@Test
fun `when serializer throws, error is logged, file deleted`() {
val expected = RuntimeException()
- whenever(fixture.serializer!!.deserializeEvent(any())).doThrow(expected)
+ whenever(fixture.serializer!!.deserializeEnvelope(any())).doThrow(expected)
val sut = fixture.getSut()
- val testFile = File(Files.createTempFile(tempDirectory, "send-cached-event-test", DiskCache.FILE_SUFFIX).toUri())
+ val testFile = File(Files.createTempFile(tempDirectory, "send-cached-event-test", EnvelopeCache.SUFFIX_ENVELOPE_FILE).toUri())
testFile.deleteOnExit()
sut.processFile(testFile, mock())
- verify(fixture.logger)!!.log(eq(SentryLevel.ERROR), eq(expected), eq("Failed to capture cached event %s"), eq(testFile.absolutePath))
+ verify(fixture.logger)!!.log(eq(SentryLevel.ERROR), eq(expected), eq("Failed to capture cached envelope %s"), eq(testFile.absolutePath))
verifyNoMoreInteractions(fixture.hub)
assertFalse(testFile.exists())
}
@Test
fun `when hub throws, file gets deleted`() {
- whenever(fixture.serializer!!.deserializeEvent(any())).thenReturn(SentryEvent())
val expected = RuntimeException()
- whenever(fixture.serializer!!.deserializeEvent(any())).doThrow(expected)
+ whenever(fixture.serializer!!.deserializeEnvelope(any())).doThrow(expected)
val sut = fixture.getSut()
- val testFile = File(Files.createTempFile(tempDirectory, "send-cached-event-test", DiskCache.FILE_SUFFIX).toUri())
+ val testFile = File(Files.createTempFile(tempDirectory, "send-cached-event-test", EnvelopeCache.SUFFIX_ENVELOPE_FILE).toUri())
testFile.deleteOnExit()
sut.processFile(testFile, any())
- verify(fixture.logger)!!.log(eq(SentryLevel.ERROR), eq(expected), eq("Failed to capture cached event %s"), eq(testFile.absolutePath))
+ verify(fixture.logger)!!.log(eq(SentryLevel.ERROR), eq(expected), eq("Failed to capture cached envelope %s"), eq(testFile.absolutePath))
verifyNoMoreInteractions(fixture.hub)
}
}
diff --git a/sentry-core/src/test/java/io/sentry/core/FileFromResources.kt b/sentry/src/test/java/io/sentry/FileFromResources.kt
similarity index 96%
rename from sentry-core/src/test/java/io/sentry/core/FileFromResources.kt
rename to sentry/src/test/java/io/sentry/FileFromResources.kt
index 541a31c77..d9c9f78ed 100644
--- a/sentry-core/src/test/java/io/sentry/core/FileFromResources.kt
+++ b/sentry/src/test/java/io/sentry/FileFromResources.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import java.io.File
import java.util.Scanner
diff --git a/sentry-core/src/test/java/io/sentry/core/GsonSerializerTest.kt b/sentry/src/test/java/io/sentry/GsonSerializerTest.kt
similarity index 99%
rename from sentry-core/src/test/java/io/sentry/core/GsonSerializerTest.kt
rename to sentry/src/test/java/io/sentry/GsonSerializerTest.kt
index bc63a0fdb..150aac161 100644
--- a/sentry-core/src/test/java/io/sentry/core/GsonSerializerTest.kt
+++ b/sentry/src/test/java/io/sentry/GsonSerializerTest.kt
@@ -1,13 +1,13 @@
-package io.sentry.core
+package io.sentry
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
-import io.sentry.core.protocol.Contexts
-import io.sentry.core.protocol.Device
-import io.sentry.core.protocol.SdkVersion
+import io.sentry.protocol.Contexts
+import io.sentry.protocol.Device
+import io.sentry.protocol.SdkVersion
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
diff --git a/sentry-core/src/test/java/io/sentry/core/HttpTransportFactoryTest.kt b/sentry/src/test/java/io/sentry/HttpTransportFactoryTest.kt
similarity index 97%
rename from sentry-core/src/test/java/io/sentry/core/HttpTransportFactoryTest.kt
rename to sentry/src/test/java/io/sentry/HttpTransportFactoryTest.kt
index ab05b1c73..2ac1a30b6 100644
--- a/sentry-core/src/test/java/io/sentry/core/HttpTransportFactoryTest.kt
+++ b/sentry/src/test/java/io/sentry/HttpTransportFactoryTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import kotlin.test.Test
import kotlin.test.assertFailsWith
diff --git a/sentry-core/src/test/java/io/sentry/core/HubTest.kt b/sentry/src/test/java/io/sentry/HubTest.kt
similarity index 99%
rename from sentry-core/src/test/java/io/sentry/core/HubTest.kt
rename to sentry/src/test/java/io/sentry/HubTest.kt
index 3c6fc1acf..89c6f803d 100644
--- a/sentry-core/src/test/java/io/sentry/core/HubTest.kt
+++ b/sentry/src/test/java/io/sentry/HubTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.anyOrNull
@@ -12,10 +12,10 @@ import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import com.nhaarman.mockitokotlin2.whenever
-import io.sentry.core.hints.SessionEndHint
-import io.sentry.core.hints.SessionStartHint
-import io.sentry.core.protocol.SentryId
-import io.sentry.core.protocol.User
+import io.sentry.hints.SessionEndHint
+import io.sentry.hints.SessionStartHint
+import io.sentry.protocol.SentryId
+import io.sentry.protocol.User
import java.io.File
import java.nio.file.Files
import java.util.Queue
diff --git a/sentry-core/src/test/java/io/sentry/core/MainEventProcessorTest.kt b/sentry/src/test/java/io/sentry/MainEventProcessorTest.kt
similarity index 97%
rename from sentry-core/src/test/java/io/sentry/core/MainEventProcessorTest.kt
rename to sentry/src/test/java/io/sentry/MainEventProcessorTest.kt
index 2f0cb67ed..50f932dd2 100644
--- a/sentry-core/src/test/java/io/sentry/core/MainEventProcessorTest.kt
+++ b/sentry/src/test/java/io/sentry/MainEventProcessorTest.kt
@@ -1,9 +1,9 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.mock
-import io.sentry.core.hints.ApplyScopeData
-import io.sentry.core.hints.Cached
-import io.sentry.core.protocol.SdkVersion
+import io.sentry.hints.ApplyScopeData
+import io.sentry.hints.Cached
+import io.sentry.protocol.SdkVersion
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
diff --git a/sentry-core/src/test/java/io/sentry/core/NoOpHubTest.kt b/sentry/src/test/java/io/sentry/NoOpHubTest.kt
similarity index 96%
rename from sentry-core/src/test/java/io/sentry/core/NoOpHubTest.kt
rename to sentry/src/test/java/io/sentry/NoOpHubTest.kt
index 5501ce062..3575a6f86 100644
--- a/sentry-core/src/test/java/io/sentry/core/NoOpHubTest.kt
+++ b/sentry/src/test/java/io/sentry/NoOpHubTest.kt
@@ -1,6 +1,6 @@
-package io.sentry.core
+package io.sentry
-import io.sentry.core.protocol.SentryId
+import io.sentry.protocol.SentryId
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
diff --git a/sentry-core/src/test/java/io/sentry/core/NoOpSentryClientTest.kt b/sentry/src/test/java/io/sentry/NoOpSentryClientTest.kt
similarity index 94%
rename from sentry-core/src/test/java/io/sentry/core/NoOpSentryClientTest.kt
rename to sentry/src/test/java/io/sentry/NoOpSentryClientTest.kt
index eca741f62..e3949b80b 100644
--- a/sentry-core/src/test/java/io/sentry/core/NoOpSentryClientTest.kt
+++ b/sentry/src/test/java/io/sentry/NoOpSentryClientTest.kt
@@ -1,6 +1,6 @@
-package io.sentry.core
+package io.sentry
-import io.sentry.core.protocol.SentryId
+import io.sentry.protocol.SentryId
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
diff --git a/sentry-core/src/test/java/io/sentry/core/NoOpSerializerTest.kt b/sentry/src/test/java/io/sentry/NoOpSerializerTest.kt
similarity index 95%
rename from sentry-core/src/test/java/io/sentry/core/NoOpSerializerTest.kt
rename to sentry/src/test/java/io/sentry/NoOpSerializerTest.kt
index 999349723..d7b4d0368 100644
--- a/sentry-core/src/test/java/io/sentry/core/NoOpSerializerTest.kt
+++ b/sentry/src/test/java/io/sentry/NoOpSerializerTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git a/sentry-core/src/test/java/io/sentry/core/OptionsContainerTest.kt b/sentry/src/test/java/io/sentry/OptionsContainerTest.kt
similarity index 92%
rename from sentry-core/src/test/java/io/sentry/core/OptionsContainerTest.kt
rename to sentry/src/test/java/io/sentry/OptionsContainerTest.kt
index 7f9f7fedf..804f239f5 100644
--- a/sentry-core/src/test/java/io/sentry/core/OptionsContainerTest.kt
+++ b/sentry/src/test/java/io/sentry/OptionsContainerTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import kotlin.test.Test
import kotlin.test.assertTrue
diff --git a/sentry-core/src/test/java/io/sentry/core/EnvelopeSenderTest.kt b/sentry/src/test/java/io/sentry/OutboxSenderTest.kt
similarity index 84%
rename from sentry-core/src/test/java/io/sentry/core/EnvelopeSenderTest.kt
rename to sentry/src/test/java/io/sentry/OutboxSenderTest.kt
index 2babeb95f..e58fee324 100644
--- a/sentry-core/src/test/java/io/sentry/core/EnvelopeSenderTest.kt
+++ b/sentry/src/test/java/io/sentry/OutboxSenderTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.argWhere
@@ -7,10 +7,9 @@ import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
-import io.sentry.core.cache.SessionCache
-import io.sentry.core.hints.Retryable
-import io.sentry.core.protocol.SentryId
-import io.sentry.core.protocol.User
+import io.sentry.cache.EnvelopeCache
+import io.sentry.hints.Retryable
+import io.sentry.protocol.SentryId
import java.io.File
import java.io.FileNotFoundException
import java.nio.file.Files
@@ -22,16 +21,23 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
-class EnvelopeSenderTest {
+class OutboxSenderTest {
private class Fixture {
var hub: IHub = mock()
var envelopeReader: IEnvelopeReader = mock()
var serializer: ISerializer = mock()
var logger: ILogger = mock()
+ var options: SentryOptions
- fun getSut(): EnvelopeSender {
- return EnvelopeSender(hub, envelopeReader, serializer, logger, 15000)
+ init {
+ options = SentryOptions()
+ options.isDebug = true
+ options.setLogger(logger)
+ }
+
+ fun getSut(): OutboxSender {
+ return OutboxSender(hub, envelopeReader, serializer, logger, 15000)
}
}
@@ -77,16 +83,17 @@ class EnvelopeSenderTest {
@Test
fun `when parser is EnvelopeReader and serializer returns SentryEnvelope, event captured, file is deleted `() {
fixture.envelopeReader = EnvelopeReader()
- val session = Session("123", User(), "env", "release")
- val expected = SentryEnvelope(SentryId("3067d54967f84f20a2adfab5119156ce"), null, setOf())
+
+ val event = SentryEvent(SentryId("9ec79c33ec9942ab8353589fcb2e04dc"), Date())
+ val expected = SentryEnvelope(SentryId("9ec79c33ec9942ab8353589fcb2e04dc"), null, setOf())
whenever(fixture.serializer.deserializeEnvelope(any())).thenReturn(expected)
- whenever(fixture.serializer.deserializeSession(any())).thenReturn(session)
+ whenever(fixture.serializer.deserializeEvent(any())).thenReturn(event)
val sut = fixture.getSut()
- val path = getTempEnvelope("envelope-session-start.txt")
+ val path = getTempEnvelope("envelope-event-attachment.txt")
assertTrue(File(path).exists()) // sanity check
sut.processEnvelopeFile(path, mock())
- verify(fixture.hub).captureEnvelope(any(), any())
+ verify(fixture.hub).captureEvent(any(), any())
assertFalse(File(path).exists())
// Additionally make sure we have no errors logged
verify(fixture.logger, never()).log(eq(SentryLevel.ERROR), any(), any())
@@ -112,9 +119,9 @@ class EnvelopeSenderTest {
fun `when parser is EnvelopeReader and serializer returns a null envelope, file error logged, no event captured `() {
fixture.envelopeReader = EnvelopeReader()
whenever(fixture.serializer.deserializeEnvelope(any())).thenReturn(null)
- whenever(fixture.serializer.deserializeSession(any())).thenReturn(null)
+ whenever(fixture.serializer.deserializeEvent(any())).thenReturn(null)
val sut = fixture.getSut()
- val path = getTempEnvelope("envelope-session-start.txt")
+ val path = getTempEnvelope("envelope-event-attachment.txt")
assertTrue(File(path).exists()) // sanity check
sut.processEnvelopeFile(path, mock())
@@ -133,7 +140,7 @@ class EnvelopeSenderTest {
@Test
fun `when hub is null, ctor throws`() {
- val clazz = Class.forName("io.sentry.core.EnvelopeSender")
+ val clazz = Class.forName("io.sentry.OutboxSender")
val ctor = clazz.getConstructor(IHub::class.java, IEnvelopeReader::class.java, ISerializer::class.java, ILogger::class.java, Long::class.java)
val params = arrayOf(null, mock(), mock(), mock(), null)
assertFailsWith { ctor.newInstance(params) }
@@ -141,7 +148,7 @@ class EnvelopeSenderTest {
@Test
fun `when envelopeReader is null, ctor throws`() {
- val clazz = Class.forName("io.sentry.core.EnvelopeSender")
+ val clazz = Class.forName("io.sentry.OutboxSender")
val ctor = clazz.getConstructor(IHub::class.java, IEnvelopeReader::class.java, ISerializer::class.java, ILogger::class.java, Long::class.java)
val params = arrayOf(mock(), null, mock(), mock(), 15000)
assertFailsWith { ctor.newInstance(params) }
@@ -149,7 +156,7 @@ class EnvelopeSenderTest {
@Test
fun `when serializer is null, ctor throws`() {
- val clazz = Class.forName("io.sentry.core.EnvelopeSender")
+ val clazz = Class.forName("io.sentry.OutboxSender")
val ctor = clazz.getConstructor(IHub::class.java, IEnvelopeReader::class.java, ISerializer::class.java, ILogger::class.java, Long::class.java)
val params = arrayOf(mock(), mock(), null, mock(), 15000)
assertFailsWith { ctor.newInstance(params) }
@@ -157,7 +164,7 @@ class EnvelopeSenderTest {
@Test
fun `when logger is null, ctor throws`() {
- val clazz = Class.forName("io.sentry.core.EnvelopeSender")
+ val clazz = Class.forName("io.sentry.OutboxSender")
val ctor = clazz.getConstructor(IHub::class.java, IEnvelopeReader::class.java, ISerializer::class.java, ILogger::class.java, Long::class.java)
val params = arrayOf(mock(), mock(), mock(), null, 15000)
assertFailsWith { ctor.newInstance(params) }
@@ -170,7 +177,7 @@ class EnvelopeSenderTest {
@Test
fun `when file name is current prefix, should be ignored`() {
- assertFalse(fixture.getSut().isRelevantFileName(SessionCache.PREFIX_CURRENT_SESSION_FILE))
+ assertFalse(fixture.getSut().isRelevantFileName(EnvelopeCache.PREFIX_CURRENT_SESSION_FILE))
}
@Test
diff --git a/sentry-core/src/test/java/io/sentry/core/SampleDsn.kt b/sentry/src/test/java/io/sentry/SampleDsn.kt
similarity index 93%
rename from sentry-core/src/test/java/io/sentry/core/SampleDsn.kt
rename to sentry/src/test/java/io/sentry/SampleDsn.kt
index 7bd705df3..0ddabd4b6 100644
--- a/sentry-core/src/test/java/io/sentry/core/SampleDsn.kt
+++ b/sentry/src/test/java/io/sentry/SampleDsn.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
// Legacy DSN includes a secret. Sentry 8 and older will require it.
const val dsnStringLegacy: String = "https://d4d82fc1c2c4032a83f3a29aa3a3aff:ed0a8589a0bb4d4793ac4c70375f3d65@fake-sentry.io:65535/2147483647"
diff --git a/sentry-core/src/test/java/io/sentry/core/ScopeTest.kt b/sentry/src/test/java/io/sentry/ScopeTest.kt
similarity index 99%
rename from sentry-core/src/test/java/io/sentry/core/ScopeTest.kt
rename to sentry/src/test/java/io/sentry/ScopeTest.kt
index ea37ae867..5c7cd39b8 100644
--- a/sentry-core/src/test/java/io/sentry/core/ScopeTest.kt
+++ b/sentry/src/test/java/io/sentry/ScopeTest.kt
@@ -1,6 +1,6 @@
-package io.sentry.core
+package io.sentry
-import io.sentry.core.protocol.User
+import io.sentry.protocol.User
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
diff --git a/sentry-core/src/test/java/io/sentry/core/SendCachedEventFireAndForgetIntegrationTest.kt b/sentry/src/test/java/io/sentry/SendCachedEnvelopeFireAndForgetIntegrationTest.kt
similarity index 76%
rename from sentry-core/src/test/java/io/sentry/core/SendCachedEventFireAndForgetIntegrationTest.kt
rename to sentry/src/test/java/io/sentry/SendCachedEnvelopeFireAndForgetIntegrationTest.kt
index 9c242ff75..7aa6ae716 100644
--- a/sentry-core/src/test/java/io/sentry/core/SendCachedEventFireAndForgetIntegrationTest.kt
+++ b/sentry/src/test/java/io/sentry/SendCachedEnvelopeFireAndForgetIntegrationTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
@@ -7,20 +7,20 @@ import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import kotlin.test.Test
import kotlin.test.assertFalse
-class SendCachedEventFireAndForgetIntegrationTest {
+class SendCachedEnvelopeFireAndForgetIntegrationTest {
private class Fixture {
var hub: IHub = mock()
var logger: ILogger = mock()
var options = SentryOptions()
- var callback = mock()
+ var callback = mock()
init {
options.isDebug = true
options.setLogger(logger)
}
- fun getSut(): SendCachedEventFireAndForgetIntegration {
- return SendCachedEventFireAndForgetIntegration(callback)
+ fun getSut(): SendCachedEnvelopeFireAndForgetIntegration {
+ return SendCachedEnvelopeFireAndForgetIntegration(callback)
}
}
@@ -55,15 +55,15 @@ class SendCachedEventFireAndForgetIntegrationTest {
@Test
fun `when Factory returns null, register logs and exit`() {
- val sut = SendCachedEventFireAndForgetIntegration(CustomFactory())
+ val sut = SendCachedEnvelopeFireAndForgetIntegration(CustomFactory())
fixture.options.cacheDirPath = "abc"
sut.register(fixture.hub, fixture.options)
verify(fixture.logger).log(eq(SentryLevel.ERROR), eq("SendFireAndForget factory is null."))
verifyNoMoreInteractions(fixture.hub)
}
- private class CustomFactory : SendCachedEventFireAndForgetIntegration.SendFireAndForgetFactory {
- override fun create(hub: IHub?, options: SentryOptions?): SendCachedEventFireAndForgetIntegration.SendFireAndForget? {
+ private class CustomFactory : SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory {
+ override fun create(hub: IHub?, options: SentryOptions?): SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForget? {
return null
}
}
diff --git a/sentry-core/src/test/java/io/sentry/core/SentryClientTest.kt b/sentry/src/test/java/io/sentry/SentryClientTest.kt
similarity index 88%
rename from sentry-core/src/test/java/io/sentry/core/SentryClientTest.kt
rename to sentry/src/test/java/io/sentry/SentryClientTest.kt
index edd509680..92e355aaa 100644
--- a/sentry-core/src/test/java/io/sentry/core/SentryClientTest.kt
+++ b/sentry/src/test/java/io/sentry/SentryClientTest.kt
@@ -1,31 +1,31 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.anyOrNull
-import com.nhaarman.mockitokotlin2.argWhere
import com.nhaarman.mockitokotlin2.check
import com.nhaarman.mockitokotlin2.eq
-import com.nhaarman.mockitokotlin2.isNull
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.mockingDetails
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import com.nhaarman.mockitokotlin2.whenever
-import io.sentry.core.hints.ApplyScopeData
-import io.sentry.core.hints.Cached
-import io.sentry.core.hints.DiskFlushNotification
-import io.sentry.core.hints.SessionEndHint
-import io.sentry.core.hints.SessionUpdateHint
-import io.sentry.core.protocol.Request
-import io.sentry.core.protocol.SdkVersion
-import io.sentry.core.protocol.SentryException
-import io.sentry.core.protocol.SentryId
-import io.sentry.core.protocol.User
-import io.sentry.core.transport.AsyncConnection
-import io.sentry.core.transport.HttpTransport
-import io.sentry.core.transport.ITransportGate
+import io.sentry.hints.ApplyScopeData
+import io.sentry.hints.Cached
+import io.sentry.hints.DiskFlushNotification
+import io.sentry.protocol.Mechanism
+import io.sentry.protocol.Request
+import io.sentry.protocol.SdkVersion
+import io.sentry.protocol.SentryException
+import io.sentry.protocol.SentryId
+import io.sentry.protocol.User
+import io.sentry.transport.AsyncConnection
+import io.sentry.transport.HttpTransport
+import io.sentry.transport.ITransportGate
+import java.io.ByteArrayInputStream
import java.io.IOException
+import java.io.InputStreamReader
+import java.lang.RuntimeException
import java.net.URL
import java.util.UUID
import kotlin.test.Ignore
@@ -47,8 +47,10 @@ class SentryClientTest {
name = "test"
version = "1.2.3"
}
+ setSerializer(GsonSerializer(mock(), envelopeReader))
}
var connection: AsyncConnection = mock()
+
fun getSut() = SentryClient(sentryOptions, connection)
}
@@ -119,17 +121,22 @@ class SentryClientTest {
val sut = fixture.getSut()
val event = SentryEvent()
sut.captureEvent(event)
- verify(fixture.connection, never()).send(event)
+ verify(fixture.connection, never()).send(any())
}
@Test
fun `when beforeSend is returns new instance, new instance is sent`() {
- val expected = SentryEvent()
+ val expected = SentryEvent().apply {
+ setTag("test", "test")
+ }
fixture.sentryOptions.setBeforeSend { _, _ -> expected }
val sut = fixture.getSut()
val actual = SentryEvent()
sut.captureEvent(actual)
- verify(fixture.connection).send(eq(expected), isNull())
+ verify(fixture.connection).send(check {
+ val event = getEventFromData(it.items.first().data)
+ assertEquals("test", event.tags["test"])
+ }, anyOrNull())
verifyNoMoreInteractions(fixture.connection)
}
@@ -156,7 +163,7 @@ class SentryClientTest {
val sut = fixture.getSut()
val expectedHint = Object()
sut.captureEvent(event, expectedHint)
- verify(fixture.connection).send(event, expectedHint)
+ verify(fixture.connection).send(any(), eq(expectedHint))
}
@Test
@@ -421,6 +428,7 @@ class SentryClientTest {
val event = SentryEvent()
val scope = createScope()
val processor = mock()
+ whenever(processor.process(any(), anyOrNull())).thenReturn(event)
scope.addEventProcessor(processor)
val sut = fixture.getSut()
@@ -478,11 +486,11 @@ class SentryClientTest {
}
@Test
- fun `When event is Fatal or not handled, mark session as Crashed`() {
+ fun `When event is non handled, mark session as Crashed`() {
val scope = Scope(fixture.sentryOptions)
scope.startSession().current
val event = SentryEvent().apply {
- level = SentryLevel.FATAL
+ exceptions = createNonHandledException()
}
fixture.getSut().updateSessionData(event, null, scope)
scope.withSession {
@@ -491,7 +499,7 @@ class SentryClientTest {
}
@Test
- fun `When event is non fatal, keep level as it is`() {
+ fun `When event is handled, keep level as it is`() {
val scope = Scope(fixture.sentryOptions)
val session = scope.startSession().current
val level = session.status
@@ -501,11 +509,11 @@ class SentryClientTest {
}
@Test
- fun `When event is Fatal, increase errorCount`() {
+ fun `When event is non handled, increase errorCount`() {
val scope = Scope(fixture.sentryOptions)
scope.startSession().current
val event = SentryEvent().apply {
- level = SentryLevel.FATAL
+ exceptions = createNonHandledException()
}
fixture.getSut().updateSessionData(event, null, scope)
scope.withSession {
@@ -529,7 +537,7 @@ class SentryClientTest {
}
@Test
- fun `When event is non fatal nor errored, do not increase errorsCount`() {
+ fun `When event is handled and not errored, do not increase errorsCount`() {
val scope = Scope(fixture.sentryOptions)
val session = scope.startSession().current
val errorCount = session.errorCount()
@@ -576,46 +584,13 @@ class SentryClientTest {
}
}
- @Test
- fun `When event comes from uncaughtException, captureSession should use SessionEndHint`() {
- fixture.sentryOptions.release = "a@1+1"
- val sut = fixture.getSut()
-
- val event = SentryEvent().apply {
- level = SentryLevel.FATAL
- }
- val scope = Scope(fixture.sentryOptions)
- scope.startSession()
- val hint = mock()
- sut.captureEvent(event, scope, hint)
- verify(fixture.connection).send(any(), argWhere {
- it is SessionEndHint
- })
- }
-
- @Test
- fun `When event is not from uncaughtException, captureSession should use SessionUpdateHint`() {
- fixture.sentryOptions.release = "a@1+1"
- val sut = fixture.getSut()
-
- val event = SentryEvent().apply {
- level = SentryLevel.FATAL
- }
- val scope = Scope(fixture.sentryOptions)
- scope.startSession()
- sut.captureEvent(event, scope)
- verify(fixture.connection).send(any(), argWhere {
- it is SessionUpdateHint
- })
- }
-
@Test
fun `when captureEvent with sampling, session is still updated`() {
fixture.sentryOptions.sampleRate = 1.0
val sut = fixture.getSut()
val event = SentryEvent().apply {
- level = SentryLevel.FATAL
+ exceptions = createNonHandledException()
}
val scope = Scope(fixture.sentryOptions)
scope.startSession().current
@@ -630,13 +605,13 @@ class SentryClientTest {
fun `when context property is missing on the event, property from scope contexts is applied`() {
val sut = fixture.getSut()
- val event = SentryEvent()
val scope = Scope(fixture.sentryOptions)
scope.setContexts("key", "value")
scope.startSession().current
- sut.captureEvent(event, scope, null)
- verify(fixture.connection).send(check {
- assertEquals("value", it.contexts["key"])
+ sut.captureEvent(SentryEvent(), scope, null)
+ verify(fixture.connection).send(check {
+ val event = getEventFromData(it.items.first().data)
+ assertEquals("value", event.contexts["key"])
}, anyOrNull())
}
@@ -650,11 +625,19 @@ class SentryClientTest {
scope.setContexts("key", "scope value")
scope.startSession().current
sut.captureEvent(event, scope, null)
- verify(fixture.connection).send(check {
- assertEquals("event value", it.contexts["key"])
+ verify(fixture.connection).send(check {
+ val eventFromData = getEventFromData(it.items.first().data)
+ assertEquals("event value", eventFromData.contexts["key"])
}, anyOrNull())
}
+ @Test
+ fun `exception thrown by an event processor is handled gracefully`() {
+ fixture.sentryOptions.addEventProcessor { _, _ -> throw RuntimeException() }
+ val sut = fixture.getSut()
+ sut.captureEvent(SentryEvent())
+ }
+
private fun createScope(): Scope {
return Scope(SentryOptions()).apply {
addBreadcrumb(Breadcrumb().apply {
@@ -695,6 +678,20 @@ class SentryClientTest {
override fun isConnected(): Boolean = false
}
+ private fun createNonHandledException(): List {
+ val exception = SentryException().apply {
+ mechanism = Mechanism().apply {
+ isHandled = false
+ }
+ }
+ return listOf(exception)
+ }
+
+ private fun getEventFromData(data: ByteArray): SentryEvent {
+ val inputStream = InputStreamReader(ByteArrayInputStream(data))
+ return fixture.sentryOptions.serializer.deserializeEvent(inputStream)
+ }
+
internal class CustomCachedApplyScopeDataHint : Cached, ApplyScopeData
internal class DiskFlushNotificationHint : DiskFlushNotification {
diff --git a/sentry-core/src/test/java/io/sentry/core/SentryEnvelopeItemTest.kt b/sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt
similarity index 92%
rename from sentry-core/src/test/java/io/sentry/core/SentryEnvelopeItemTest.kt
rename to sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt
index cf19266e9..9cc15c9ae 100644
--- a/sentry-core/src/test/java/io/sentry/core/SentryEnvelopeItemTest.kt
+++ b/sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt
@@ -1,7 +1,7 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.mock
-import io.sentry.core.protocol.User
+import io.sentry.protocol.User
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
diff --git a/sentry-core/src/test/java/io/sentry/core/SentryEnvelopeTest.kt b/sentry/src/test/java/io/sentry/SentryEnvelopeTest.kt
similarity index 99%
rename from sentry-core/src/test/java/io/sentry/core/SentryEnvelopeTest.kt
rename to sentry/src/test/java/io/sentry/SentryEnvelopeTest.kt
index d6582211d..227490dd4 100644
--- a/sentry-core/src/test/java/io/sentry/core/SentryEnvelopeTest.kt
+++ b/sentry/src/test/java/io/sentry/SentryEnvelopeTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.mock
diff --git a/sentry-core/src/test/java/io/sentry/core/SentryEventTest.kt b/sentry/src/test/java/io/sentry/SentryEventTest.kt
similarity index 83%
rename from sentry-core/src/test/java/io/sentry/core/SentryEventTest.kt
rename to sentry/src/test/java/io/sentry/SentryEventTest.kt
index 71aa0f3c9..76b4afb06 100644
--- a/sentry-core/src/test/java/io/sentry/core/SentryEventTest.kt
+++ b/sentry/src/test/java/io/sentry/SentryEventTest.kt
@@ -1,9 +1,9 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.mock
-import io.sentry.core.exception.ExceptionMechanismException
-import io.sentry.core.protocol.Mechanism
-import io.sentry.core.protocol.SentryId
+import io.sentry.exception.ExceptionMechanismException
+import io.sentry.protocol.Mechanism
+import io.sentry.protocol.SentryId
import java.time.Instant
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
@@ -39,13 +39,6 @@ class SentryEventTest {
assertEquals(expected, DateUtils.getTimestampIsoFormat(actual.timestamp))
}
- @Test
- fun `if level Fatal it should return isCrashed=true`() {
- val event = SentryEvent()
- event.level = SentryLevel.FATAL
- assertTrue(event.isCrashed)
- }
-
@Test
fun `if mechanism is not handled, it should return isCrashed=true`() {
val mechanism = Mechanism()
@@ -58,11 +51,10 @@ class SentryEventTest {
}
@Test
- fun `if mechanism is handled and level is not fatal, it should return isCrashed=false`() {
+ fun `if mechanism is handled, it should return isCrashed=false`() {
val mechanism = Mechanism()
mechanism.isHandled = true
val event = SentryEvent()
- event.level = SentryLevel.ERROR
val factory = SentryExceptionFactory(mock())
val sentryExceptions = factory.getSentryExceptions(ExceptionMechanismException(mechanism, Throwable(), Thread()))
event.exceptions = sentryExceptions
@@ -70,11 +62,10 @@ class SentryEventTest {
}
@Test
- fun `if mechanism nas not handled flag and level is not fatal, it should return isCrashed=false`() {
+ fun `if mechanism handled flag is null, it should return isCrashed=false`() {
val mechanism = Mechanism()
mechanism.isHandled = null
val event = SentryEvent()
- event.level = SentryLevel.ERROR
val factory = SentryExceptionFactory(mock())
val sentryExceptions = factory.getSentryExceptions(ExceptionMechanismException(mechanism, Throwable(), Thread()))
event.exceptions = sentryExceptions
@@ -82,6 +73,14 @@ class SentryEventTest {
}
@Test
+ fun `if mechanism is not set, it should return isCrashed=false`() {
+ val event = SentryEvent()
+ val factory = SentryExceptionFactory(mock())
+ val sentryExceptions = factory.getSentryExceptions(RuntimeException(Throwable()))
+ event.exceptions = sentryExceptions
+ assertFalse(event.isCrashed)
+ }
+
fun `adds breadcrumb with string as a parameter`() {
val event = SentryEvent()
event.addBreadcrumb("breadcrumb")
diff --git a/sentry-core/src/test/java/io/sentry/core/SentryExceptionFactoryTest.kt b/sentry/src/test/java/io/sentry/SentryExceptionFactoryTest.kt
similarity index 96%
rename from sentry-core/src/test/java/io/sentry/core/SentryExceptionFactoryTest.kt
rename to sentry/src/test/java/io/sentry/SentryExceptionFactoryTest.kt
index 471680856..5e77ad01d 100644
--- a/sentry-core/src/test/java/io/sentry/core/SentryExceptionFactoryTest.kt
+++ b/sentry/src/test/java/io/sentry/SentryExceptionFactoryTest.kt
@@ -1,7 +1,7 @@
-package io.sentry.core
+package io.sentry
-import io.sentry.core.exception.ExceptionMechanismException
-import io.sentry.core.protocol.Mechanism
+import io.sentry.exception.ExceptionMechanismException
+import io.sentry.protocol.Mechanism
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
diff --git a/sentry-core/src/test/java/io/sentry/core/SentryExecutorServiceTest.kt b/sentry/src/test/java/io/sentry/SentryExecutorServiceTest.kt
similarity index 99%
rename from sentry-core/src/test/java/io/sentry/core/SentryExecutorServiceTest.kt
rename to sentry/src/test/java/io/sentry/SentryExecutorServiceTest.kt
index a4b9bfffc..fc5845717 100644
--- a/sentry-core/src/test/java/io/sentry/core/SentryExecutorServiceTest.kt
+++ b/sentry/src/test/java/io/sentry/SentryExecutorServiceTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.mock
diff --git a/sentry-core/src/test/java/io/sentry/core/SentryItemTypeTest.kt b/sentry/src/test/java/io/sentry/SentryItemTypeTest.kt
similarity index 93%
rename from sentry-core/src/test/java/io/sentry/core/SentryItemTypeTest.kt
rename to sentry/src/test/java/io/sentry/SentryItemTypeTest.kt
index af6b47dc6..945c53ebb 100644
--- a/sentry-core/src/test/java/io/sentry/core/SentryItemTypeTest.kt
+++ b/sentry/src/test/java/io/sentry/SentryItemTypeTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git a/sentry-core/src/test/java/io/sentry/core/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt
similarity index 89%
rename from sentry-core/src/test/java/io/sentry/core/SentryOptionsTest.kt
rename to sentry/src/test/java/io/sentry/SentryOptionsTest.kt
index 5068179e0..81d260b29 100644
--- a/sentry-core/src/test/java/io/sentry/core/SentryOptionsTest.kt
+++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import java.io.File
import kotlin.test.Test
@@ -106,19 +106,6 @@ class SentryOptionsTest {
assertEquals("${File.separator}test${File.separator}outbox", options.outboxPath)
}
- @Test
- fun `when there's no cacheDirPath, sessionPath returns null`() {
- val options = SentryOptions()
- assertNull(options.sessionsPath)
- }
-
- @Test
- fun `when cacheDirPath is set, sessionPath concatenate sessions path`() {
- val options = SentryOptions()
- options.cacheDirPath = "${File.separator}test"
- assertEquals("${File.separator}test${File.separator}sessions", options.sessionsPath)
- }
-
@Test
fun `SentryOptions creates SentryExecutorService on ctor`() {
val options = SentryOptions()
@@ -135,7 +122,7 @@ class SentryOptionsTest {
assertEquals(BuildConfig.VERSION_NAME, sdkVersion.version)
assertTrue(sdkVersion.packages!!.any {
- it.name == "maven:sentry-core" &&
+ it.name == "maven:sentry" &&
it.version == BuildConfig.VERSION_NAME
})
}
diff --git a/sentry-core/src/test/java/io/sentry/core/SentryStackTraceFactoryTest.kt b/sentry/src/test/java/io/sentry/SentryStackTraceFactoryTest.kt
similarity index 99%
rename from sentry-core/src/test/java/io/sentry/core/SentryStackTraceFactoryTest.kt
rename to sentry/src/test/java/io/sentry/SentryStackTraceFactoryTest.kt
index 7be89bd68..29e48101f 100644
--- a/sentry-core/src/test/java/io/sentry/core/SentryStackTraceFactoryTest.kt
+++ b/sentry/src/test/java/io/sentry/SentryStackTraceFactoryTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git a/sentry-core/src/test/java/io/sentry/core/SentryTest.kt b/sentry/src/test/java/io/sentry/SentryTest.kt
similarity index 88%
rename from sentry-core/src/test/java/io/sentry/core/SentryTest.kt
rename to sentry/src/test/java/io/sentry/SentryTest.kt
index 348946fa9..ca5a83151 100644
--- a/sentry-core/src/test/java/io/sentry/core/SentryTest.kt
+++ b/sentry/src/test/java/io/sentry/SentryTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
@@ -46,20 +46,6 @@ class SentryTest {
file.deleteOnExit()
}
- @Test
- fun `sessionDir should be created at initialization`() {
- var sentryOptions: SentryOptions? = null
- Sentry.init {
- it.dsn = "http://key@localhost/proj"
- it.cacheDirPath = getTempPath()
- sentryOptions = it
- }
-
- val file = File(sentryOptions!!.sessionsPath!!)
- assertTrue(file.exists())
- file.deleteOnExit()
- }
-
@Test
fun `Init sets SystemOutLogger if logger is NoOp and debug is enabled`() {
var sentryOptions: SentryOptions? = null
diff --git a/sentry-core/src/test/java/io/sentry/core/SentryThreadFactoryTest.kt b/sentry/src/test/java/io/sentry/SentryThreadFactoryTest.kt
similarity index 99%
rename from sentry-core/src/test/java/io/sentry/core/SentryThreadFactoryTest.kt
rename to sentry/src/test/java/io/sentry/SentryThreadFactoryTest.kt
index 1fc98bc49..fecec2b9c 100644
--- a/sentry-core/src/test/java/io/sentry/core/SentryThreadFactoryTest.kt
+++ b/sentry/src/test/java/io/sentry/SentryThreadFactoryTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git a/sentry-core/src/test/java/io/sentry/core/SentryValuesTest.kt b/sentry/src/test/java/io/sentry/SentryValuesTest.kt
similarity index 95%
rename from sentry-core/src/test/java/io/sentry/core/SentryValuesTest.kt
rename to sentry/src/test/java/io/sentry/SentryValuesTest.kt
index 63d7dc517..b8d4eb35a 100644
--- a/sentry-core/src/test/java/io/sentry/core/SentryValuesTest.kt
+++ b/sentry/src/test/java/io/sentry/SentryValuesTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git a/sentry-core/src/test/java/io/sentry/core/SessionTest.kt b/sentry/src/test/java/io/sentry/SessionTest.kt
similarity index 98%
rename from sentry-core/src/test/java/io/sentry/core/SessionTest.kt
rename to sentry/src/test/java/io/sentry/SessionTest.kt
index 8c5d29245..ccae83061 100644
--- a/sentry-core/src/test/java/io/sentry/core/SessionTest.kt
+++ b/sentry/src/test/java/io/sentry/SessionTest.kt
@@ -1,8 +1,8 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
-import io.sentry.core.protocol.User
+import io.sentry.protocol.User
import java.util.Date
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git a/sentry-core/src/test/java/io/sentry/core/ShutdownHookIntegrationTest.kt b/sentry/src/test/java/io/sentry/ShutdownHookIntegrationTest.kt
similarity index 95%
rename from sentry-core/src/test/java/io/sentry/core/ShutdownHookIntegrationTest.kt
rename to sentry/src/test/java/io/sentry/ShutdownHookIntegrationTest.kt
index 436b05a91..61c0a90bd 100644
--- a/sentry-core/src/test/java/io/sentry/core/ShutdownHookIntegrationTest.kt
+++ b/sentry/src/test/java/io/sentry/ShutdownHookIntegrationTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.mock
diff --git a/sentry-core/src/test/java/io/sentry/core/StringExtensions.kt b/sentry/src/test/java/io/sentry/StringExtensions.kt
similarity index 87%
rename from sentry-core/src/test/java/io/sentry/core/StringExtensions.kt
rename to sentry/src/test/java/io/sentry/StringExtensions.kt
index 9543cfea2..e82001814 100644
--- a/sentry-core/src/test/java/io/sentry/core/StringExtensions.kt
+++ b/sentry/src/test/java/io/sentry/StringExtensions.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import java.io.ByteArrayInputStream
diff --git a/sentry-core/src/test/java/io/sentry/core/UncaughtExceptionHandlerIntegrationTest.kt b/sentry/src/test/java/io/sentry/UncaughtExceptionHandlerIntegrationTest.kt
similarity index 96%
rename from sentry-core/src/test/java/io/sentry/core/UncaughtExceptionHandlerIntegrationTest.kt
rename to sentry/src/test/java/io/sentry/UncaughtExceptionHandlerIntegrationTest.kt
index 7ddc0c0dc..4c5e07c31 100644
--- a/sentry-core/src/test/java/io/sentry/core/UncaughtExceptionHandlerIntegrationTest.kt
+++ b/sentry/src/test/java/io/sentry/UncaughtExceptionHandlerIntegrationTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core
+package io.sentry
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.argWhere
@@ -7,9 +7,9 @@ import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever
-import io.sentry.core.exception.ExceptionMechanismException
-import io.sentry.core.protocol.SentryId
-import io.sentry.core.util.noFlushTimeout
+import io.sentry.exception.ExceptionMechanismException
+import io.sentry.protocol.SentryId
+import io.sentry.util.noFlushTimeout
import java.io.File
import java.nio.file.Files
import kotlin.test.AfterTest
diff --git a/sentry/src/test/java/io/sentry/cache/CacheStrategyTest.kt b/sentry/src/test/java/io/sentry/cache/CacheStrategyTest.kt
new file mode 100644
index 000000000..c5692f9b7
--- /dev/null
+++ b/sentry/src/test/java/io/sentry/cache/CacheStrategyTest.kt
@@ -0,0 +1,186 @@
+package io.sentry.cache
+
+import com.nhaarman.mockitokotlin2.mock
+import io.sentry.DateUtils
+import io.sentry.GsonSerializer
+import io.sentry.SentryEnvelope
+import io.sentry.SentryOptions
+import io.sentry.Session
+import java.io.ByteArrayInputStream
+import java.io.File
+import java.io.InputStreamReader
+import java.nio.file.Files
+import java.util.UUID
+import kotlin.test.AfterTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+class CacheStrategyTest {
+
+ private class Fixture {
+ val dir = Files.createTempDirectory("sentry-disk-cache-test").toAbsolutePath().toFile()
+ val sentryOptions = SentryOptions().apply {
+ setSerializer(mock())
+ }
+
+ fun getSUT(maxSize: Int = 5, options: SentryOptions = sentryOptions): CacheStrategy {
+ return CustomCache(options, dir.absolutePath, maxSize)
+ }
+ }
+
+ private val fixture = Fixture()
+
+ @Test
+ fun `isDirectoryValid returns true if a valid directory`() {
+ val sut = fixture.getSUT()
+
+ // sanity check
+ assertTrue(fixture.dir.isDirectory)
+
+ // this test assumes that the dir. has write/read permission.
+ assertTrue(sut.isDirectoryValid)
+ }
+
+ @Test
+ fun `Sort files from the oldest to the newest`() {
+ val sut = fixture.getSUT(3)
+
+ val files = createTempFilesSortByOldestToNewest()
+ val reverseFiles = files.reversedArray()
+
+ sut.rotateCacheIfNeeded(reverseFiles)
+
+ assertEquals(files[0].absolutePath, reverseFiles[0].absolutePath)
+ assertEquals(files[1].absolutePath, reverseFiles[1].absolutePath)
+ assertEquals(files[2].absolutePath, reverseFiles[2].absolutePath)
+ }
+
+ @Test
+ fun `Rotate cache folder to save new file`() {
+ val sut = fixture.getSUT(3)
+
+ val files = createTempFilesSortByOldestToNewest()
+ val reverseFiles = files.reversedArray()
+
+ sut.rotateCacheIfNeeded(reverseFiles)
+
+ assertFalse(files[0].exists())
+ assertTrue(files[1].exists())
+ assertTrue(files[2].exists())
+ }
+
+ @Test
+ fun `do not move init flag if state is not ok`() {
+ val options = SentryOptions().apply {
+ setSerializer(GsonSerializer(mock(), envelopeReader))
+ }
+ val sut = fixture.getSUT(3, getOptionsWithRealSerializer())
+
+ val files = createTempFilesSortByOldestToNewest()
+
+ saveSessionToFile(files[0], sut, Session.State.Crashed, null)
+
+ saveSessionToFile(files[1], sut, Session.State.Exited, null)
+
+ saveSessionToFile(files[2], sut, Session.State.Exited, null)
+
+ sut.rotateCacheIfNeeded(files)
+
+ // files[0] has been deleted because of rotation
+ for (i in 1..2) {
+ val expectedSession = getSessionFromFile(files[i], sut)
+
+ assertNull(expectedSession.init)
+ }
+ }
+
+ @Test
+ fun `move init flag if state is ok`() {
+ val options = SentryOptions().apply {
+ setSerializer(GsonSerializer(mock(), envelopeReader))
+ }
+ val sut = fixture.getSUT(3, options)
+
+ val files = createTempFilesSortByOldestToNewest()
+
+ val okSession = createSessionMockData(Session.State.Ok, true)
+ val okEnvelope = SentryEnvelope.fromSession(sut.serializer, okSession, null)
+ sut.serializer.serialize(okEnvelope, files[0].writer())
+
+ val updatedOkSession = okSession.clone()
+ updatedOkSession.update(null, null, true)
+ val updatedOkEnvelope = SentryEnvelope.fromSession(sut.serializer, updatedOkSession, null)
+ sut.serializer.serialize(updatedOkEnvelope, files[1].writer())
+
+ saveSessionToFile(files[2], sut, Session.State.Exited, null)
+
+ sut.rotateCacheIfNeeded(files)
+
+ // files[1] should be the one with the init flag true
+ val expectedSession = getSessionFromFile(files[1], sut)
+
+ assertTrue(expectedSession.init!!)
+ }
+
+ @AfterTest
+ fun shutdown() {
+ fixture.dir.listFiles()?.forEach {
+ it.deleteRecursively()
+ }
+ }
+
+ private class CustomCache(options: SentryOptions, path: String, maxSize: Int) : CacheStrategy(options, path, maxSize)
+
+ private fun createTempFilesSortByOldestToNewest(): Array {
+ val f1 = Files.createTempFile(fixture.dir.toPath(), "f1", ".json").toFile()
+ f1.setLastModified(DateUtils.getDateTime("2020-03-27T08:52:58.015Z").time)
+
+ val f2 = Files.createTempFile(fixture.dir.toPath(), "f2", ".json").toFile()
+ f2.setLastModified(DateUtils.getDateTime("2020-03-27T08:52:59.015Z").time)
+
+ val f3 = Files.createTempFile(fixture.dir.toPath(), "f3", ".json").toFile()
+ f3.setLastModified(DateUtils.getDateTime("2020-03-27T08:53:00.015Z").time)
+
+ return arrayOf(f1, f2, f3)
+ }
+
+ private fun createSessionMockData(state: Session.State = Session.State.Ok, init: Boolean? = true): Session =
+ Session(
+ state,
+ DateUtils.getDateTime("2020-02-07T14:16:00.000Z"),
+ DateUtils.getDateTime("2020-02-07T14:16:00.000Z"),
+ 2,
+ "123",
+ UUID.fromString("c81d4e2e-bcf2-11e6-869b-7df92533d2db"),
+ init,
+ 123456.toLong(),
+ 6000.toDouble(),
+ "127.0.0.1",
+ "jamesBond",
+ "debug",
+ "io.sentry@1.0+123"
+ )
+
+ private fun getSessionFromFile(file: File, sut: CacheStrategy): Session {
+ val envelope = sut.serializer.deserializeEnvelope(file.inputStream())
+ val item = envelope.items.first()
+
+ val reader = InputStreamReader(ByteArrayInputStream(item.data), Charsets.UTF_8)
+ return sut.serializer.deserializeSession(reader)
+ }
+
+ private fun saveSessionToFile(file: File, sut: CacheStrategy, state: Session.State = Session.State.Ok, init: Boolean? = true) {
+ val okSession = createSessionMockData(Session.State.Ok, init)
+ val okEnvelope = SentryEnvelope.fromSession(sut.serializer, okSession, null)
+ sut.serializer.serialize(okEnvelope, file.writer())
+ }
+
+ private fun getOptionsWithRealSerializer(): SentryOptions {
+ return SentryOptions().apply {
+ setSerializer(GsonSerializer(mock(), envelopeReader))
+ }
+ }
+}
diff --git a/sentry-core/src/test/java/io/sentry/core/cache/SessionCacheTest.kt b/sentry/src/test/java/io/sentry/cache/EnvelopeCacheTest.kt
similarity index 65%
rename from sentry-core/src/test/java/io/sentry/core/cache/SessionCacheTest.kt
rename to sentry/src/test/java/io/sentry/cache/EnvelopeCacheTest.kt
index b4d2e1586..c7b4ac1e8 100644
--- a/sentry-core/src/test/java/io/sentry/core/cache/SessionCacheTest.kt
+++ b/sentry/src/test/java/io/sentry/cache/EnvelopeCacheTest.kt
@@ -1,23 +1,21 @@
-package io.sentry.core.cache
+package io.sentry.cache
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
-import io.sentry.core.ILogger
-import io.sentry.core.ISerializer
-import io.sentry.core.SentryEnvelope
-import io.sentry.core.SentryLevel
-import io.sentry.core.SentryOptions
-import io.sentry.core.Session
-import io.sentry.core.cache.SessionCache.PREFIX_CURRENT_SESSION_FILE
-import io.sentry.core.cache.SessionCache.SUFFIX_CURRENT_SESSION_FILE
-import io.sentry.core.cache.SessionCache.SUFFIX_ENVELOPE_FILE
-import io.sentry.core.hints.SessionEndHint
-import io.sentry.core.hints.SessionStartHint
-import io.sentry.core.hints.SessionUpdateHint
-import io.sentry.core.protocol.User
+import io.sentry.ILogger
+import io.sentry.ISerializer
+import io.sentry.SentryEnvelope
+import io.sentry.SentryLevel
+import io.sentry.SentryOptions
+import io.sentry.Session
+import io.sentry.cache.EnvelopeCache.PREFIX_CURRENT_SESSION_FILE
+import io.sentry.cache.EnvelopeCache.SUFFIX_CURRENT_SESSION_FILE
+import io.sentry.hints.SessionEndHint
+import io.sentry.hints.SessionStartHint
+import io.sentry.protocol.User
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
@@ -27,7 +25,7 @@ import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
-class SessionCacheTest {
+class EnvelopeCacheTest {
private class Fixture {
val maxSize = 5
val dir: Path = Files.createTempDirectory("sentry-session-cache-test")
@@ -36,9 +34,7 @@ class SessionCacheTest {
val logger = mock()
fun getSUT(): IEnvelopeCache {
- options.sessionsDirSize = maxSize
options.cacheDirPath = dir.toAbsolutePath().toFile().absolutePath
- File(options.sessionsPath!!).mkdirs()
whenever(serializer.deserializeSession(any())).thenAnswer {
Session("dis", User(), "env", "rel")
@@ -48,7 +44,7 @@ class SessionCacheTest {
options.setSerializer(serializer)
options.isDebug = true
- return SessionCache(options)
+ return EnvelopeCache(options)
}
}
@@ -58,7 +54,7 @@ class SessionCacheTest {
fun `stores envelopes`() {
val cache = fixture.getSUT()
- val file = File(fixture.options.sessionsPath!!)
+ val file = File(fixture.options.cacheDirPath!!)
val nofFiles = { file.list()?.size }
assertEquals(0, nofFiles())
@@ -70,24 +66,6 @@ class SessionCacheTest {
file.deleteRecursively()
}
- @Test
- fun `limits the number of stored envelopes`() {
- val cache = fixture.getSUT()
-
- val file = File(fixture.options.sessionsPath!!)
- val nofFiles = { file.list()?.size }
-
- assertEquals(0, nofFiles())
-
- (1..fixture.maxSize + 1).forEach { _ ->
- cache.store(SentryEnvelope.fromSession(fixture.serializer, createSession(), null))
- }
-
- assertEquals(fixture.maxSize, nofFiles())
-
- file.deleteRecursively()
- }
-
@Test
fun `tolerates discarding unknown envelope`() {
val cache = fixture.getSUT()
@@ -101,12 +79,12 @@ class SessionCacheTest {
fun `creates current file on session start`() {
val cache = fixture.getSUT()
- val file = File(fixture.options.sessionsPath!!)
+ val file = File(fixture.options.cacheDirPath!!)
val envelope = SentryEnvelope.fromSession(fixture.serializer, createSession(), null)
cache.store(envelope, SessionStartHint())
- val currentFile = File(fixture.options.sessionsPath!!, "$PREFIX_CURRENT_SESSION_FILE$SUFFIX_CURRENT_SESSION_FILE")
+ val currentFile = File(fixture.options.cacheDirPath!!, "$PREFIX_CURRENT_SESSION_FILE$SUFFIX_CURRENT_SESSION_FILE")
assertTrue(currentFile.exists())
file.deleteRecursively()
@@ -116,12 +94,12 @@ class SessionCacheTest {
fun `deletes current file on session end`() {
val cache = fixture.getSUT()
- val file = File(fixture.options.sessionsPath!!)
+ val file = File(fixture.options.cacheDirPath!!)
val envelope = SentryEnvelope.fromSession(fixture.serializer, createSession(), null)
cache.store(envelope, SessionStartHint())
- val currentFile = File(fixture.options.sessionsPath!!, "$PREFIX_CURRENT_SESSION_FILE$SUFFIX_CURRENT_SESSION_FILE")
+ val currentFile = File(fixture.options.cacheDirPath!!, "$PREFIX_CURRENT_SESSION_FILE$SUFFIX_CURRENT_SESSION_FILE")
assertTrue(currentFile.exists())
cache.store(envelope, SessionEndHint())
@@ -130,39 +108,16 @@ class SessionCacheTest {
file.deleteRecursively()
}
- @Test
- fun `updates current file on session update, but do not create a new envelope`() {
- val cache = fixture.getSUT()
-
- val file = File(fixture.options.sessionsPath!!)
-
- val envelope = SentryEnvelope.fromSession(fixture.serializer, createSession(), null)
- cache.store(envelope, SessionStartHint())
-
- val currentFile = File(fixture.options.sessionsPath!!, "$PREFIX_CURRENT_SESSION_FILE$SUFFIX_CURRENT_SESSION_FILE")
- assertTrue(currentFile.exists())
-
- val newEnvelope = SentryEnvelope.fromSession(fixture.serializer, createSession(), null)
-
- cache.store(newEnvelope, SessionUpdateHint())
- assertTrue(currentFile.exists())
-
- val newFile = File(file.absolutePath, "${newEnvelope.header.eventId}$SUFFIX_ENVELOPE_FILE")
- assertFalse(newFile.exists())
-
- file.deleteRecursively()
- }
-
@Test
fun `updates current file on session update and read it back`() {
val cache = fixture.getSUT()
- val file = File(fixture.options.sessionsPath!!)
+ val file = File(fixture.options.cacheDirPath!!)
val envelope = SentryEnvelope.fromSession(fixture.serializer, createSession(), null)
cache.store(envelope, SessionStartHint())
- val currentFile = File(fixture.options.sessionsPath!!, "$PREFIX_CURRENT_SESSION_FILE$SUFFIX_CURRENT_SESSION_FILE")
+ val currentFile = File(fixture.options.cacheDirPath!!, "$PREFIX_CURRENT_SESSION_FILE$SUFFIX_CURRENT_SESSION_FILE")
assertTrue(currentFile.exists())
val session = fixture.serializer.deserializeSession(currentFile.bufferedReader(Charsets.UTF_8))
@@ -190,8 +145,8 @@ class SessionCacheTest {
fun `when session start, current file already exist and crash marker file exist, end session and delete marker file`() {
val cache = fixture.getSUT()
- val file = File(fixture.options.sessionsPath!!)
- val markerFile = File(fixture.options.cacheDirPath!!, SessionCache.CRASH_MARKER_FILE)
+ val file = File(fixture.options.cacheDirPath!!)
+ val markerFile = File(fixture.options.cacheDirPath!!, EnvelopeCache.CRASH_MARKER_FILE)
markerFile.mkdirs()
assertTrue(markerFile.exists())
@@ -209,8 +164,8 @@ class SessionCacheTest {
@Test
fun `when session start, current file already exist and crash marker file exist, end session with given timestamp`() {
val cache = fixture.getSUT()
- val file = File(fixture.options.sessionsPath!!)
- val markerFile = File(fixture.options.cacheDirPath!!, SessionCache.CRASH_MARKER_FILE)
+ val file = File(fixture.options.cacheDirPath!!)
+ val markerFile = File(fixture.options.cacheDirPath!!, EnvelopeCache.CRASH_MARKER_FILE)
File(fixture.options.cacheDirPath!!, ".sentry-native").mkdirs()
markerFile.createNewFile()
val date = "2020-02-07T14:16:00.000Z"
diff --git a/sentry-core/src/test/java/io/sentry/core/protocol/AppTest.kt b/sentry/src/test/java/io/sentry/protocol/AppTest.kt
similarity index 98%
rename from sentry-core/src/test/java/io/sentry/core/protocol/AppTest.kt
rename to sentry/src/test/java/io/sentry/protocol/AppTest.kt
index 30c53e5fe..0fb672764 100644
--- a/sentry-core/src/test/java/io/sentry/core/protocol/AppTest.kt
+++ b/sentry/src/test/java/io/sentry/protocol/AppTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core.protocol
+package io.sentry.protocol
import java.util.Date
import kotlin.test.Test
diff --git a/sentry-core/src/test/java/io/sentry/core/protocol/BrowserTest.kt b/sentry/src/test/java/io/sentry/protocol/BrowserTest.kt
similarity index 97%
rename from sentry-core/src/test/java/io/sentry/core/protocol/BrowserTest.kt
rename to sentry/src/test/java/io/sentry/protocol/BrowserTest.kt
index 2c322e062..feabd36e0 100644
--- a/sentry-core/src/test/java/io/sentry/core/protocol/BrowserTest.kt
+++ b/sentry/src/test/java/io/sentry/protocol/BrowserTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core.protocol
+package io.sentry.protocol
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git a/sentry-core/src/test/java/io/sentry/core/protocol/ContextsTest.kt b/sentry/src/test/java/io/sentry/protocol/ContextsTest.kt
similarity index 97%
rename from sentry-core/src/test/java/io/sentry/core/protocol/ContextsTest.kt
rename to sentry/src/test/java/io/sentry/protocol/ContextsTest.kt
index 2fe525cbc..f265a5467 100644
--- a/sentry-core/src/test/java/io/sentry/core/protocol/ContextsTest.kt
+++ b/sentry/src/test/java/io/sentry/protocol/ContextsTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core.protocol
+package io.sentry.protocol
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
diff --git a/sentry-core/src/test/java/io/sentry/core/protocol/DeviceTest.kt b/sentry/src/test/java/io/sentry/protocol/DeviceTest.kt
similarity index 99%
rename from sentry-core/src/test/java/io/sentry/core/protocol/DeviceTest.kt
rename to sentry/src/test/java/io/sentry/protocol/DeviceTest.kt
index bc354be1e..028b9b946 100644
--- a/sentry-core/src/test/java/io/sentry/core/protocol/DeviceTest.kt
+++ b/sentry/src/test/java/io/sentry/protocol/DeviceTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core.protocol
+package io.sentry.protocol
import java.util.Date
import java.util.TimeZone
diff --git a/sentry-core/src/test/java/io/sentry/core/protocol/GpuTest.kt b/sentry/src/test/java/io/sentry/protocol/GpuTest.kt
similarity index 97%
rename from sentry-core/src/test/java/io/sentry/core/protocol/GpuTest.kt
rename to sentry/src/test/java/io/sentry/protocol/GpuTest.kt
index 91088b465..9614111cc 100644
--- a/sentry-core/src/test/java/io/sentry/core/protocol/GpuTest.kt
+++ b/sentry/src/test/java/io/sentry/protocol/GpuTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core.protocol
+package io.sentry.protocol
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git a/sentry-core/src/test/java/io/sentry/core/protocol/OperatingSystemTest.kt b/sentry/src/test/java/io/sentry/protocol/OperatingSystemTest.kt
similarity index 97%
rename from sentry-core/src/test/java/io/sentry/core/protocol/OperatingSystemTest.kt
rename to sentry/src/test/java/io/sentry/protocol/OperatingSystemTest.kt
index 7ea8606b2..a59b3f43c 100644
--- a/sentry-core/src/test/java/io/sentry/core/protocol/OperatingSystemTest.kt
+++ b/sentry/src/test/java/io/sentry/protocol/OperatingSystemTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core.protocol
+package io.sentry.protocol
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git a/sentry-core/src/test/java/io/sentry/core/protocol/SentryRuntimeTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryRuntimeTest.kt
similarity index 97%
rename from sentry-core/src/test/java/io/sentry/core/protocol/SentryRuntimeTest.kt
rename to sentry/src/test/java/io/sentry/protocol/SentryRuntimeTest.kt
index 2b07f7e83..2389fc843 100644
--- a/sentry-core/src/test/java/io/sentry/core/protocol/SentryRuntimeTest.kt
+++ b/sentry/src/test/java/io/sentry/protocol/SentryRuntimeTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core.protocol
+package io.sentry.protocol
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git a/sentry-core/src/test/java/io/sentry/core/protocol/UserTest.kt b/sentry/src/test/java/io/sentry/protocol/UserTest.kt
similarity index 98%
rename from sentry-core/src/test/java/io/sentry/core/protocol/UserTest.kt
rename to sentry/src/test/java/io/sentry/protocol/UserTest.kt
index 1fd1e8f34..5c52f57b7 100644
--- a/sentry-core/src/test/java/io/sentry/core/protocol/UserTest.kt
+++ b/sentry/src/test/java/io/sentry/protocol/UserTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core.protocol
+package io.sentry.protocol
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git a/sentry-core/src/test/java/io/sentry/core/transport/AsyncConnectionTest.kt b/sentry/src/test/java/io/sentry/transport/AsyncConnectionTest.kt
similarity index 59%
rename from sentry-core/src/test/java/io/sentry/core/transport/AsyncConnectionTest.kt
rename to sentry/src/test/java/io/sentry/transport/AsyncConnectionTest.kt
index ae96c3cec..9198dbc48 100644
--- a/sentry-core/src/test/java/io/sentry/core/transport/AsyncConnectionTest.kt
+++ b/sentry/src/test/java/io/sentry/transport/AsyncConnectionTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core.transport
+package io.sentry.transport
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.anyOrNull
@@ -9,17 +9,16 @@ import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
-import io.sentry.core.CachedEvent
-import io.sentry.core.SentryEnvelope
-import io.sentry.core.SentryEnvelopeHeader
-import io.sentry.core.SentryEnvelopeItem
-import io.sentry.core.SentryEvent
-import io.sentry.core.SentryOptions
-import io.sentry.core.Session
-import io.sentry.core.cache.IEnvelopeCache
-import io.sentry.core.cache.IEventCache
-import io.sentry.core.dsnString
-import io.sentry.core.protocol.User
+import io.sentry.CachedEvent
+import io.sentry.SentryEnvelope
+import io.sentry.SentryEnvelopeHeader
+import io.sentry.SentryEnvelopeItem
+import io.sentry.SentryEvent
+import io.sentry.SentryOptions
+import io.sentry.Session
+import io.sentry.cache.IEnvelopeCache
+import io.sentry.dsnString
+import io.sentry.protocol.User
import java.io.IOException
import java.util.concurrent.ExecutorService
import kotlin.test.Test
@@ -30,8 +29,7 @@ class AsyncConnectionTest {
private class Fixture {
var transport = mock()
var transportGate = mock()
- var eventCache = mock()
- var sessionCache = mock()
+ var envelopeCache = mock()
var executor = mock()
var sentryOptions: SentryOptions = SentryOptions().apply {
dsn = dsnString
@@ -46,68 +44,34 @@ class AsyncConnectionTest {
}
fun getSUT(): AsyncConnection {
- return AsyncConnection(transport, transportGate, eventCache, sessionCache, executor, sentryOptions)
+ return AsyncConnection(transport, transportGate, envelopeCache, executor, sentryOptions)
}
}
private val fixture = Fixture()
@Test
- fun `successful send discards the event from cache`() {
- // given
- val ev = mock()
- whenever(fixture.transportGate.isConnected).thenReturn(true)
- whenever(fixture.transport.send(any())).thenReturn(TransportResult.success())
-
- // when
- fixture.getSUT().send(ev)
-
- // then
- val order = inOrder(fixture.transport, fixture.eventCache)
-
- // because storeBeforeSend is enabled by default
- order.verify(fixture.eventCache).store(eq(ev))
-
- order.verify(fixture.transport).send(eq(ev))
- order.verify(fixture.eventCache).discard(eq(ev))
- }
-
- @Test
- fun `successful send discards the session from cache`() {
+ fun `successful send discards the envelope from cache`() {
// given
val envelope = SentryEnvelope.fromSession(fixture.sentryOptions.serializer, createSession(), null)
whenever(fixture.transportGate.isConnected).thenReturn(true)
- whenever(fixture.transport.send(any())).thenReturn(TransportResult.success())
+ whenever(fixture.transport.send(any())).thenReturn(TransportResult.success())
// when
fixture.getSUT().send(envelope)
// then
- val order = inOrder(fixture.transport, fixture.sessionCache)
+ val order = inOrder(fixture.transport, fixture.envelopeCache)
// because storeBeforeSend is enabled by default
- order.verify(fixture.sessionCache).store(eq(envelope), anyOrNull())
+ order.verify(fixture.envelopeCache).store(eq(envelope), anyOrNull())
order.verify(fixture.transport).send(eq(envelope))
- order.verify(fixture.sessionCache).discard(eq(envelope))
- }
-
- @Test
- fun `stores event in cache if sending is not allowed`() {
- // given
- val ev = mock()
- whenever(fixture.transportGate.isConnected).thenReturn(false)
-
- // when
- fixture.getSUT().send(ev)
-
- // then
- verify(fixture.eventCache).store(eq(ev))
- verify(fixture.transport).isRetryAfter(any())
+ order.verify(fixture.envelopeCache).discard(eq(envelope))
}
@Test
- fun `stores session in cache if sending is not allowed`() {
+ fun `stores envelope in cache if sending is not allowed`() {
// given
val envelope = SentryEnvelope.fromSession(fixture.sentryOptions.serializer, createSession(), null)
whenever(fixture.transportGate.isConnected).thenReturn(false)
@@ -116,40 +80,16 @@ class AsyncConnectionTest {
fixture.getSUT().send(envelope)
// then
- verify(fixture.sessionCache).store(eq(envelope), anyOrNull())
+ verify(fixture.envelopeCache).store(eq(envelope), anyOrNull())
verify(fixture.transport).isRetryAfter(any())
}
@Test
- fun `stores event after unsuccessful send`() {
- // given
- val ev = mock()
- whenever(fixture.transportGate.isConnected).thenReturn(true)
- whenever(fixture.transport.send(any())).thenReturn(TransportResult.error(500))
-
- // when
- try {
- fixture.getSUT().send(ev)
- } catch (e: IllegalStateException) {
- // expected - this is how the AsyncConnection signals failure to the executor for it to retry
- }
-
- // then
- val order = inOrder(fixture.transport, fixture.eventCache)
-
- // because storeBeforeSend is enabled by default
- order.verify(fixture.eventCache).store(eq(ev))
-
- order.verify(fixture.transport).send(eq(ev))
- verify(fixture.eventCache, never()).discard(any())
- }
-
- @Test
- fun `stores session after unsuccessful send`() {
+ fun `stores envelope after unsuccessful send`() {
// given
val envelope = SentryEnvelope.fromSession(fixture.sentryOptions.serializer, createSession(), null)
whenever(fixture.transportGate.isConnected).thenReturn(true)
- whenever(fixture.transport.send(any())).thenReturn(TransportResult.error(500))
+ whenever(fixture.transport.send(any())).thenReturn(TransportResult.error(500))
// when
try {
@@ -159,41 +99,21 @@ class AsyncConnectionTest {
}
// then
- val order = inOrder(fixture.transport, fixture.sessionCache)
+ val order = inOrder(fixture.transport, fixture.envelopeCache)
// because storeBeforeSend is enabled by default
- order.verify(fixture.sessionCache).store(eq(envelope), anyOrNull())
+ order.verify(fixture.envelopeCache).store(eq(envelope), anyOrNull())
order.verify(fixture.transport).send(eq(envelope))
- verify(fixture.eventCache, never()).discard(any())
- }
-
- @Test
- fun `stores event after send failure`() {
- // given
- val ev = mock()
- whenever(fixture.transportGate.isConnected).thenReturn(true)
- whenever(fixture.transport.send(any())).thenThrow(IOException())
-
- // when
- try {
- fixture.getSUT().send(ev)
- } catch (e: IllegalStateException) {
- // expected - this is how the AsyncConnection signals failure to the executor for it to retry
- }
-
- // then
- val order = inOrder(fixture.transport, fixture.eventCache)
- order.verify(fixture.transport).send(eq(ev))
- verify(fixture.eventCache, never()).discard(any())
+ verify(fixture.envelopeCache, never()).discard(any())
}
@Test
- fun `stores session after send failure`() {
+ fun `stores envelope after send failure`() {
// given
val envelope = SentryEnvelope.fromSession(fixture.sentryOptions.serializer, createSession(), null)
whenever(fixture.transportGate.isConnected).thenReturn(true)
- whenever(fixture.transport.send(any())).thenThrow(IOException())
+ whenever(fixture.transport.send(any())).thenThrow(IOException())
// when
try {
@@ -203,9 +123,9 @@ class AsyncConnectionTest {
}
// then
- val order = inOrder(fixture.transport, fixture.sessionCache)
+ val order = inOrder(fixture.transport, fixture.envelopeCache)
order.verify(fixture.transport).send(eq(envelope))
- verify(fixture.sessionCache, never()).discard(any())
+ verify(fixture.envelopeCache, never()).discard(any())
}
@Test
@@ -213,9 +133,10 @@ class AsyncConnectionTest {
// given
val ev = mock()
whenever(fixture.transport.isRetryAfter(any())).thenReturn(true)
+ val envelope = SentryEnvelope.fromEvent(fixture.sentryOptions.serializer, ev, null)
// when
- fixture.getSUT().send(ev)
+ fixture.getSUT().send(envelope)
// then
verify(fixture.executor, never()).submit(any())
@@ -226,9 +147,10 @@ class AsyncConnectionTest {
// given
val ev = mock()
whenever(fixture.transport.isRetryAfter(any())).thenReturn(false)
+ val envelope = SentryEnvelope.fromEvent(fixture.sentryOptions.serializer, ev, null)
// when
- fixture.getSUT().send(ev)
+ fixture.getSUT().send(envelope)
// then
verify(fixture.executor).submit(any())
@@ -257,7 +179,7 @@ class AsyncConnectionTest {
fixture.getSUT().send(envelope, CachedEvent())
// then
- verify(fixture.sessionCache).discard(any())
+ verify(fixture.envelopeCache).discard(any())
}
@Test
@@ -270,7 +192,7 @@ class AsyncConnectionTest {
fixture.getSUT().send(envelope)
// then
- verify(fixture.sessionCache, never()).discard(any())
+ verify(fixture.envelopeCache, never()).discard(any())
}
@Test
@@ -307,12 +229,13 @@ class AsyncConnectionTest {
// given
val ev = mock()
whenever(fixture.transport.isRetryAfter(any())).thenReturn(true)
+ val envelope = SentryEnvelope.fromEvent(fixture.sentryOptions.serializer, ev, null)
// when
- fixture.getSUT().send(ev, CachedEvent())
+ fixture.getSUT().send(envelope, CachedEvent())
// then
- verify(fixture.eventCache).discard(any())
+ verify(fixture.envelopeCache).discard(any())
}
@Test
@@ -320,12 +243,13 @@ class AsyncConnectionTest {
// given
val ev = mock()
whenever(fixture.transport.isRetryAfter(any())).thenReturn(true)
+ val envelope = SentryEnvelope.fromEvent(fixture.sentryOptions.serializer, ev, null)
// when
- fixture.getSUT().send(ev)
+ fixture.getSUT().send(envelope)
// then
- verify(fixture.eventCache, never()).discard(any())
+ verify(fixture.envelopeCache, never()).discard(any())
}
private fun createSession(): Session {
diff --git a/sentry-core/src/test/java/io/sentry/core/transport/HttpTransportTest.kt b/sentry/src/test/java/io/sentry/transport/HttpTransportTest.kt
similarity index 72%
rename from sentry-core/src/test/java/io/sentry/core/transport/HttpTransportTest.kt
rename to sentry/src/test/java/io/sentry/transport/HttpTransportTest.kt
index 50f678753..b7b82b6d3 100644
--- a/sentry-core/src/test/java/io/sentry/core/transport/HttpTransportTest.kt
+++ b/sentry/src/test/java/io/sentry/transport/HttpTransportTest.kt
@@ -1,16 +1,16 @@
-package io.sentry.core.transport
+package io.sentry.transport
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
-import io.sentry.core.ISerializer
-import io.sentry.core.SentryEnvelope
-import io.sentry.core.SentryEvent
-import io.sentry.core.SentryOptions
-import io.sentry.core.Session
-import io.sentry.core.protocol.User
+import io.sentry.ISerializer
+import io.sentry.SentryEnvelope
+import io.sentry.SentryEvent
+import io.sentry.SentryOptions
+import io.sentry.Session
+import io.sentry.protocol.User
import java.io.IOException
import java.net.HttpURLConnection
import java.net.Proxy
@@ -45,11 +45,7 @@ class HttpTransportTest {
options.proxy = proxy
return object : HttpTransport(options, requestUpdater, connectionTimeout, readTimeout, bypassSecurity, dsn, currentDateProvider) {
- override fun open(proxy: Proxy?): HttpURLConnection {
- return connection
- }
-
- override fun open(url: URL, proxy: Proxy?): HttpURLConnection {
+ override fun open(): HttpURLConnection {
return connection
}
}
@@ -58,19 +54,6 @@ class HttpTransportTest {
private val fixture = Fixture()
- @Test
- fun `test serializes event`() {
- val transport = fixture.getSUT()
- whenever(fixture.connection.responseCode).thenReturn(200)
-
- val event = SentryEvent()
-
- val result = transport.send(event)
-
- verify(fixture.serializer).serialize(eq(event), any())
- assertTrue(result.isSuccess)
- }
-
@Test
fun `test serializes envelope`() {
val transport = fixture.getSUT()
@@ -84,24 +67,6 @@ class HttpTransportTest {
assertTrue(result.isSuccess)
}
- @Test
- fun `uses Retry-After header if X-Sentry-Rate-Limit is not set when sending an event`() {
- val transport = fixture.getSUT()
-
- throwOnEventSerialize()
- whenever(fixture.connection.getHeaderField(eq("Retry-After"))).thenReturn("30")
- whenever(fixture.connection.responseCode).thenReturn(429)
- whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0)
-
- val event = SentryEvent()
-
- val result = transport.send(event)
-
- verify(fixture.serializer).serialize(eq(event), any())
- assertFalse(result.isSuccess)
- assertTrue(transport.isRetryAfter("event"))
- }
-
@Test
fun `uses Retry-After header if X-Sentry-Rate-Limit is not set when sending an envelope`() {
val transport = fixture.getSUT()
@@ -120,22 +85,6 @@ class HttpTransportTest {
assertTrue(transport.isRetryAfter("session"))
}
- @Test
- fun `passes on the response code on error when sending an event`() {
- val transport = fixture.getSUT()
-
- throwOnEventSerialize()
- whenever(fixture.connection.responseCode).thenReturn(1234)
-
- val event = SentryEvent()
-
- val result = transport.send(event)
-
- verify(fixture.serializer).serialize(eq(event), any())
- assertFalse(result.isSuccess)
- assertEquals(1234, result.responseCode)
- }
-
@Test
fun `passes on the response code on error when sending an envelope`() {
val transport = fixture.getSUT()
@@ -152,23 +101,6 @@ class HttpTransportTest {
assertEquals(1234, result.responseCode)
}
- @Test
- fun `uses the default retry interval if there is no Retry-After header when sending an event`() {
- val transport = fixture.getSUT()
-
- throwOnEventSerialize()
- whenever(fixture.connection.responseCode).thenReturn(429)
- whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0)
-
- val event = SentryEvent()
-
- val result = transport.send(event)
-
- verify(fixture.serializer).serialize(eq(event), any())
- assertFalse(result.isSuccess)
- assertTrue(transport.isRetryAfter("event"))
- }
-
@Test
fun `uses the default retry interval if there is no Retry-After header when sending an envelope`() {
val transport = fixture.getSUT()
@@ -186,22 +118,6 @@ class HttpTransportTest {
assertTrue(transport.isRetryAfter("session"))
}
- @Test
- fun `failure to get response code doesn't break sending an event`() {
- val transport = fixture.getSUT()
-
- throwOnEventSerialize()
- whenever(fixture.connection.responseCode).thenThrow(IOException())
-
- val event = SentryEvent()
-
- val result = transport.send(event)
-
- verify(fixture.serializer).serialize(eq(event), any())
- assertFalse(result.isSuccess)
- assertEquals(-1, result.responseCode)
- }
-
@Test
fun `failure to get response code doesn't break sending an envelope`() {
val transport = fixture.getSUT()
@@ -228,11 +144,10 @@ class HttpTransportTest {
.thenReturn("50:transaction:key, 2700:default;error;security:organization")
whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0)
- val event = SentryEvent()
-
- val result = transport.send(event)
+ val envelope = createEnvelope()
+ val result = transport.send(envelope)
- verify(fixture.serializer).serialize(eq(event), any())
+ verify(fixture.serializer).serialize(eq(envelope), any())
assertFalse(result.isSuccess)
assertTrue(transport.isRetryAfter("event"))
}
@@ -246,11 +161,10 @@ class HttpTransportTest {
.thenReturn("50:transaction:key, 1:default;error;security:organization")
whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0, 0, 1001)
- val event = SentryEvent()
-
- val result = transport.send(event)
+ val envelope = createEnvelope()
+ val result = transport.send(envelope)
- verify(fixture.serializer).serialize(eq(event), any())
+ verify(fixture.serializer).serialize(eq(envelope), any())
assertFalse(result.isSuccess)
assertFalse(transport.isRetryAfter("event"))
}
@@ -264,9 +178,7 @@ class HttpTransportTest {
.thenReturn("50:transaction:key, 2700:default;error;security:organization")
whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0)
- val event = SentryEvent()
-
- transport.send(event)
+ transport.send(createEnvelope())
assertTrue(transport.isRetryAfter("transaction"))
assertTrue(transport.isRetryAfter("event"))
@@ -281,9 +193,8 @@ class HttpTransportTest {
.thenReturn("1:transaction:key, 1:default;error;security:organization")
whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0, 0, 1001)
- val event = SentryEvent()
+ transport.send(createEnvelope())
- transport.send(event)
assertFalse(transport.isRetryAfter("transaction"))
assertFalse(transport.isRetryAfter("event"))
}
@@ -297,9 +208,7 @@ class HttpTransportTest {
.thenReturn("50::key")
whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0)
- val event = SentryEvent()
-
- transport.send(event)
+ transport.send(createEnvelope())
assertTrue(transport.isRetryAfter("event"))
}
@@ -313,9 +222,7 @@ class HttpTransportTest {
.thenReturn("60:default;foobar;error;security:organization")
whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0)
- val event = SentryEvent()
-
- transport.send(event)
+ transport.send(createEnvelope())
assertFalse(transport.isRetryAfter("foobar"))
}
@@ -328,9 +235,7 @@ class HttpTransportTest {
.thenReturn("1::key, 60:default;error;security:organization")
whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0, 0, 1001)
- val event = SentryEvent()
-
- transport.send(event)
+ transport.send(createEnvelope())
assertTrue(transport.isRetryAfter("event"))
}
@@ -343,9 +248,7 @@ class HttpTransportTest {
.thenReturn("60:error:key, 1:error:organization")
whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0, 0, 1001)
- val event = SentryEvent()
-
- transport.send(event)
+ transport.send(createEnvelope())
assertTrue(transport.isRetryAfter("event"))
}
@@ -358,9 +261,7 @@ class HttpTransportTest {
.thenReturn("1:error:key, 5:error:organization")
whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0, 0, 1001)
- val event = SentryEvent()
-
- transport.send(event)
+ transport.send(createEnvelope())
assertTrue(transport.isRetryAfter("event"))
}
@@ -376,4 +277,8 @@ class HttpTransportTest {
private fun throwOnEnvelopeSerialize() {
whenever(fixture.serializer.serialize(any(), any())).thenThrow(IOException())
}
+
+ private fun createEnvelope(event: SentryEvent = SentryEvent()): SentryEnvelope {
+ return SentryEnvelope.fromEvent(fixture.serializer, event, null)
+ }
}
diff --git a/sentry-core/src/test/java/io/sentry/core/transport/QueuedThreadPoolExecutorTest.kt b/sentry/src/test/java/io/sentry/transport/QueuedThreadPoolExecutorTest.kt
similarity index 99%
rename from sentry-core/src/test/java/io/sentry/core/transport/QueuedThreadPoolExecutorTest.kt
rename to sentry/src/test/java/io/sentry/transport/QueuedThreadPoolExecutorTest.kt
index f60612287..db8a85012 100644
--- a/sentry-core/src/test/java/io/sentry/core/transport/QueuedThreadPoolExecutorTest.kt
+++ b/sentry/src/test/java/io/sentry/transport/QueuedThreadPoolExecutorTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core.transport
+package io.sentry.transport
import com.nhaarman.mockitokotlin2.mock
import java.util.concurrent.CountDownLatch
diff --git a/sentry-core/src/test/java/io/sentry/core/transport/StdoutTransportTest.kt b/sentry/src/test/java/io/sentry/transport/StdoutTransportTest.kt
similarity index 61%
rename from sentry-core/src/test/java/io/sentry/core/transport/StdoutTransportTest.kt
rename to sentry/src/test/java/io/sentry/transport/StdoutTransportTest.kt
index fc2fe1f1d..6fa4e1dcd 100644
--- a/sentry-core/src/test/java/io/sentry/core/transport/StdoutTransportTest.kt
+++ b/sentry/src/test/java/io/sentry/transport/StdoutTransportTest.kt
@@ -1,12 +1,12 @@
-package io.sentry.core.transport
+package io.sentry.transport
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
-import io.sentry.core.ISerializer
-import io.sentry.core.SentryEvent
-import io.sentry.core.SentryOptions
+import io.sentry.ISerializer
+import io.sentry.SentryEnvelope
+import io.sentry.SentryEvent
import kotlin.test.Test
import kotlin.test.assertTrue
@@ -15,9 +15,6 @@ class StdoutTransportTest {
val serializer = mock()
fun getSUT(): ITransport {
- val options = SentryOptions()
- options.setSerializer(serializer)
-
return StdoutTransport(serializer)
}
}
@@ -25,13 +22,14 @@ class StdoutTransportTest {
private val fixture = Fixture()
@Test
- fun `test serializes event`() {
+ fun `test serializes envelope`() {
val transport = fixture.getSUT()
val event = SentryEvent()
+ val envelope = SentryEnvelope.fromEvent(fixture.serializer, event, null)
- val result = transport.send(event)
+ val result = transport.send(envelope)
- verify(fixture.serializer).serialize(eq(event), any())
+ verify(fixture.serializer).serialize(eq(envelope), any())
assertTrue(result.isSuccess)
}
}
diff --git a/sentry-core/src/test/java/io/sentry/core/util/ApplyScopeUtilsTest.kt b/sentry/src/test/java/io/sentry/util/ApplyScopeUtilsTest.kt
similarity index 88%
rename from sentry-core/src/test/java/io/sentry/core/util/ApplyScopeUtilsTest.kt
rename to sentry/src/test/java/io/sentry/util/ApplyScopeUtilsTest.kt
index 1d5fd136d..7b7a774d8 100644
--- a/sentry-core/src/test/java/io/sentry/core/util/ApplyScopeUtilsTest.kt
+++ b/sentry/src/test/java/io/sentry/util/ApplyScopeUtilsTest.kt
@@ -1,8 +1,8 @@
-package io.sentry.core.util
+package io.sentry.util
import com.nhaarman.mockitokotlin2.mock
-import io.sentry.core.hints.ApplyScopeData
-import io.sentry.core.hints.Cached
+import io.sentry.hints.ApplyScopeData
+import io.sentry.hints.Cached
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
diff --git a/sentry-core/src/test/java/io/sentry/core/util/Extensions.kt b/sentry/src/test/java/io/sentry/util/Extensions.kt
similarity index 64%
rename from sentry-core/src/test/java/io/sentry/core/util/Extensions.kt
rename to sentry/src/test/java/io/sentry/util/Extensions.kt
index b4391c4d8..c62b670dc 100644
--- a/sentry-core/src/test/java/io/sentry/core/util/Extensions.kt
+++ b/sentry/src/test/java/io/sentry/util/Extensions.kt
@@ -1,6 +1,6 @@
-package io.sentry.core.util
+package io.sentry.util
-import io.sentry.core.SentryOptions
+import io.sentry.SentryOptions
fun SentryOptions.noFlushTimeout(): SentryOptions {
return this.apply {
diff --git a/sentry-core/src/test/java/io/sentry/core/util/StringUtilsTest.kt b/sentry/src/test/java/io/sentry/util/StringUtilsTest.kt
similarity index 98%
rename from sentry-core/src/test/java/io/sentry/core/util/StringUtilsTest.kt
rename to sentry/src/test/java/io/sentry/util/StringUtilsTest.kt
index 553b443f0..7127a9536 100644
--- a/sentry-core/src/test/java/io/sentry/core/util/StringUtilsTest.kt
+++ b/sentry/src/test/java/io/sentry/util/StringUtilsTest.kt
@@ -1,4 +1,4 @@
-package io.sentry.core.util
+package io.sentry.util
import kotlin.test.Test
import kotlin.test.assertEquals
diff --git a/sentry-core/src/test/resources/envelope-event-attachment.txt b/sentry/src/test/resources/envelope-event-attachment.txt
similarity index 100%
rename from sentry-core/src/test/resources/envelope-event-attachment.txt
rename to sentry/src/test/resources/envelope-event-attachment.txt
diff --git a/sentry-core/src/test/resources/envelope-session-start.txt b/sentry/src/test/resources/envelope-session-start.txt
similarity index 100%
rename from sentry-core/src/test/resources/envelope-session-start.txt
rename to sentry/src/test/resources/envelope-session-start.txt
diff --git a/sentry-core/src/test/resources/envelope_session.txt b/sentry/src/test/resources/envelope_session.txt
similarity index 100%
rename from sentry-core/src/test/resources/envelope_session.txt
rename to sentry/src/test/resources/envelope_session.txt
diff --git a/sentry-core/src/test/resources/envelope_session_sdkversion.txt b/sentry/src/test/resources/envelope_session_sdkversion.txt
similarity index 100%
rename from sentry-core/src/test/resources/envelope_session_sdkversion.txt
rename to sentry/src/test/resources/envelope_session_sdkversion.txt
diff --git a/sentry-core/src/test/resources/event.json b/sentry/src/test/resources/event.json
similarity index 100%
rename from sentry-core/src/test/resources/event.json
rename to sentry/src/test/resources/event.json
diff --git a/sentry-core/src/test/resources/event_breadcrumb_data.json b/sentry/src/test/resources/event_breadcrumb_data.json
similarity index 100%
rename from sentry-core/src/test/resources/event_breadcrumb_data.json
rename to sentry/src/test/resources/event_breadcrumb_data.json
diff --git a/sentry-core/src/test/resources/event_with_contexts.json b/sentry/src/test/resources/event_with_contexts.json
similarity index 100%
rename from sentry-core/src/test/resources/event_with_contexts.json
rename to sentry/src/test/resources/event_with_contexts.json
diff --git a/sentry/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/sentry/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 000000000..1f0955d45
--- /dev/null
+++ b/sentry/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/sentry-core/src/test/resources/session.json b/sentry/src/test/resources/session.json
similarity index 100%
rename from sentry-core/src/test/resources/session.json
rename to sentry/src/test/resources/session.json
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 2cbd97ad2..4b56a066c 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,10 +1,19 @@
-rootProject.name = "sentry"
+rootProject.name = "sentry-root"
rootProject.buildFileName = "build.gradle.kts"
include("sentry-android",
- "sentry-android-ndk",
- "sentry-android-core",
- "sentry-core",
- "sentry-samples:sentry-samples-android",
- "sentry-samples:sentry-samples-console",
- "sentry-android-timber")
+ "sentry-android-ndk",
+ "sentry-android-core",
+ "sentry",
+ "sentry-test-support",
+ "sentry-log4j2",
+ "sentry-logback",
+ "sentry-spring",
+ "sentry-spring-boot-starter",
+ "sentry-android-timber",
+ "sentry-samples:sentry-samples-android",
+ "sentry-samples:sentry-samples-console",
+ "sentry-samples:sentry-samples-log4j2",
+ "sentry-samples:sentry-samples-logback",
+ "sentry-samples:sentry-samples-spring",
+ "sentry-samples:sentry-samples-spring-boot")