diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 4919ee6c7..ded8f5615 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -44,13 +44,13 @@ jobs: uses: actions/cache@v1 with: path: ~/.gradle/caches/ - key: cache-gradle-cache-2 + key: cache-gradle-cache-3 - name: Cache Gradle Wrapper uses: actions/cache@v1 with: path: ~/.gradle/wrapper/ - key: cache-gradle-wrapper-2 + key: cache-gradle-wrapper-3 # uses eskatos/gradle-command-action, so we don't care for ./gradlew or gradlew.bat diff --git a/README.md b/README.md index c25eee02f..d8369ee3a 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,15 @@

-Sentry SDK for Android +Sentry SDK for Java and Android =========== -[![Travis](https://img.shields.io/travis/getsentry/sentry-android?label=Travis)](https://travis-ci.com/getsentry/sentry-android) -[![AppVeyor](https://img.shields.io/appveyor/build/sentry/sentry-android?label=AppVeyor)](https://ci.appveyor.com/project/sentry/sentry-android/branch/master) -[![GH Workflow](https://img.shields.io/github/workflow/status/getsentry/sentry-android/Workflow%20Ubuntu%20macOS?label=GH%20Workflow)](https://github.com/getsentry/sentry-android/actions) -[![Tests](https://img.shields.io/appveyor/tests/sentry/sentry-android/master?compact_message)](https://ci.appveyor.com/project/sentry/sentry-android/branch/master/tests) -[![codecov](https://codecov.io/gh/getsentry/sentry-android/branch/master/graph/badge.svg)](https://codecov.io/gh/getsentry/sentry-android) -[![Language grade: Java](https://img.shields.io/lgtm/grade/java/g/getsentry/sentry-android.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/getsentry/sentry-android/context:java) -[![Total alerts](https://img.shields.io/lgtm/alerts/g/getsentry/sentry-android.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/getsentry/sentry-android/alerts/) +[![Travis](https://img.shields.io/travis/getsentry/sentry-java/ref/sentry-java-2?label=Travis)](https://travis-ci.com/getsentry/sentry-java) +[![AppVeyor](https://img.shields.io/appveyor/build/sentry/sentry-java?label=AppVeyor)](https://ci.appveyor.com/project/sentry/sentry-java) +[![GH Workflow](https://img.shields.io/github/workflow/status/getsentry/sentry-java/Workflow%20Ubuntu%20macOS?label=GH%20Workflow)](https://github.com/getsentry/sentry-java/actions) +[![Tests](https://img.shields.io/appveyor/tests/sentry/sentry-java/ref/sentry-java-2?compact_message)](https://ci.appveyor.com/project/sentry/sentry-java/branch/ref/sentry-java-2/tests) +[![codecov](https://codecov.io/gh/getsentry/sentry-java/branch/ref/sentry-java-2/graph/badge.svg)](https://codecov.io/gh/getsentry/sentry-java) +[![Language grade: Java](https://img.shields.io/lgtm/grade/java/g/getsentry/sentry-java.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/getsentry/sentry-java/context:java) +[![Total alerts](https://img.shields.io/lgtm/alerts/g/getsentry/sentry-java/ref/sentry-java-2.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/getsentry/sentry-java/alerts/) | Packages | bintray | API | | ---------------------- | ------- | ------- | @@ -21,9 +21,20 @@ Sentry SDK for Android | sentry-android-core | [![sentry-android-core](https://img.shields.io/bintray/v/getsentry/sentry-android/io.sentry:sentry-android-core)](https://bintray.com/getsentry/sentry-android/io.sentry:sentry-android-core?tab=overview) | 14 | | sentry-android-ndk | [![sentry-android-ndk](https://img.shields.io/bintray/v/getsentry/sentry-android/io.sentry:sentry-android-ndk)](https://bintray.com/getsentry/sentry-android/io.sentry:sentry-android-ndk?tab=overview) | 16 | | sentry-android-timber | [![sentry-android-timber](https://img.shields.io/bintray/v/getsentry/sentry-android/io.sentry:sentry-android-timber)](https://bintray.com/getsentry/sentry-android/io.sentry:sentry-android-timber?tab=overview) | 14 | -| sentry-core | [![sentry-core](https://img.shields.io/bintray/v/getsentry/sentry-android/io.sentry:sentry-core)](https://bintray.com/getsentry/sentry-android/io.sentry:sentry-core?tab=overview) | 14 | +| sentry | [![sentry](https://img.shields.io/bintray/v/getsentry/sentry-java/io.sentry:sentry)](https://bintray.com/getsentry/sentry-java/io.sentry:sentry?tab=overview) | 14 | +| sentry-spring-boot-starter | [![sentry-spring-boot-starter](https://img.shields.io/bintray/v/getsentry/sentry-java/io.sentry:sentry-spring-boot-starter)](https://bintray.com/getsentry/sentry-java/io.sentry:sentry-spring-boot-starter?tab=overview) | | +| sentry-spring | [![sentry-spring](https://img.shields.io/bintray/v/getsentry/sentry-java/io.sentry:sentry-spring)](https://bintray.com/getsentry/sentry-java/io.sentry:sentry-spring?tab=overview) | | +| sentry-log4j | [![sentry-log4j](https://img.shields.io/bintray/v/getsentry/sentry-java/io.sentry:sentry-log4j)](https://bintray.com/getsentry/sentry-java/io.sentry:sentry-log4j?tab=overview) | | +| sentry-log4j2 | [![sentry-log4j2](https://img.shields.io/bintray/v/getsentry/sentry-java/io.sentry:sentry-log4j2)](https://bintray.com/getsentry/sentry-java/io.sentry:sentry-log4j2?tab=overview) | | -# Docs + +# Java SDK 2.0 Docs + +The Java SDK documentation [can be found on docs.sentry.io](https://docs.sentry.io/platforms/java/). + +Java SDK version 1.x [can still be found here](https://docs.sentry.io/clients/java/). + +# Android Docs That's the initial page of the release [2.x and its docs](https://docs.sentry.io/platforms/android). @@ -54,7 +65,8 @@ This directory is also included in `.gitignore` not to be shown as pending chang # Resources -* [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/android/) +* [![Java Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/java/) +* [![Android Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/android/) * [![Forum](https://img.shields.io/badge/forum-sentry-green.svg)](https://forum.sentry.io/c/sdks) * [![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/Ww9hbqr) * [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-sentry-green.svg)](http://stackoverflow.com/questions/tagged/sentry) diff --git a/build.gradle.kts b/build.gradle.kts index 42d9b10d6..33f2fb4a8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -51,7 +51,7 @@ allprojects { dependsOn("cleanTest") } withType { - options.compilerArgs.addAll(arrayOf("-Xlint:all", "-Werror", "-Xlint:-classfile")) + options.compilerArgs.addAll(arrayOf("-Xlint:all", "-Werror", "-Xlint:-classfile", "-Xlint:-processing")) } } } @@ -65,10 +65,11 @@ spotless { } kotlin { - ktlint() target("**/*.kt") + ktlint() } kotlinGradle { + target("**/*.kts") ktlint() } } diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index 6c74ab308..3ebc6dd1e 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -1,12 +1,21 @@ +import java.math.BigDecimal + object Config { val kotlinVersion = "1.4.0" val kotlinStdLib = "stdlib-jdk8" + val springBootVersion = "2.3.3.RELEASE" + // Spring is currently not compatible with Kotlin 1.4 + val springKotlinCompatibleLanguageVersion = "1.3" + object BuildPlugins { val androidGradle = "com.android.tools.build:gradle:4.0.1" val kotlinGradlePlugin = "gradle-plugin" val buildConfig = "com.github.gmazzo.buildconfig" val buildConfigVersion = "2.0.2" + val springBoot = "org.springframework.boot" + val springDependencyManagement = "io.spring.dependency-management" + val springDependencyManagementVersion = "1.0.10.RELEASE" } object Android { @@ -32,6 +41,26 @@ object Config { private val lifecycleVersion = "2.2.0" val lifecycleProcess = "androidx.lifecycle:lifecycle-process:$lifecycleVersion" val lifecycleCommonJava8 = "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion" + + val logbackVersion = "1.2.3" + val logbackClassic = "ch.qos.logback:logback-classic:$logbackVersion" + + val log4j2Version = "2.13.3" + val log4j2Api = "org.apache.logging.log4j:log4j-api:$log4j2Version" + val log4j2Core = "org.apache.logging.log4j:log4j-core:$log4j2Version" + + val springBootStarter = "org.springframework.boot:spring-boot-starter:$springBootVersion" + val springBootStarterTest = "org.springframework.boot:spring-boot-starter-test:$springBootVersion" + val springBootStarterWeb = "org.springframework.boot:spring-boot-starter-web:$springBootVersion" + val springBootStarterSecurity = "org.springframework.boot:spring-boot-starter-security:$springBootVersion" + + val springWeb = "org.springframework:spring-webmvc" + val servletApi = "javax.servlet:javax.servlet-api" + } + + object AnnotationProcessors { + val springBootAutoConfigure = "org.springframework.boot:spring-boot-autoconfigure-processor" + val springBootConfiguration = "org.springframework.boot:spring-boot-configuration-processor" } object TestLibs { @@ -48,7 +77,10 @@ object Config { } object QualityPlugins { - val jacocoVersion = "0.8.5" + object Jacoco { + val version = "0.8.5" + val minimumCoverage = BigDecimal.valueOf(0.6) + } val spotless = "com.diffplug.spotless" val spotlessVersion = "5.3.0" val errorProne = "net.ltgt.errorprone" @@ -63,6 +95,10 @@ object Config { object Sentry { val SENTRY_JAVA_SDK_NAME = "sentry.java" val SENTRY_ANDROID_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.android" + val SENTRY_LOGBACK_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.logback" + val SENTRY_LOG4J2_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.log4j2" + val SENTRY_SPRING_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring" + val SENTRY_SPRING_BOOT_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot" val group = "io.sentry" val description = "SDK for sentry.io" val website = "https://sentry.io" diff --git a/gradle.properties b/gradle.properties index fbcd63e15..e93deafa5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,8 +8,8 @@ org.gradle.caching=true android.useAndroidX=true # Release information -buildVersionCode=20038 -versionName=2.3.2-SNAPSHOT +buildVersionCode=20039 +versionName=3.0.0-alpha.2-SNAPSHOT # disable renderscript, it's enabled by default android.defaults.buildfeatures.renderscript=false @@ -23,6 +23,9 @@ android.defaults.buildfeatures.aidl=false # disable Resource Values generation android.defaults.buildfeatures.resvalues=false +# disable automatically adding Kotlin stdlib to compile dependencies +kotlin.stdlib.default.dependency=false + # TODO: Enable Prefab https://android-developers.googleblog.com/2020/02/native-dependencies-in-android-studio-40.html # android.enablePrefab=true # android.prefabVersion=1.0.0 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c0535..e708b1c02 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6c9a22477..12d38de6a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index fbd7c5158..4f906e0c8 100755 --- a/gradlew +++ b/gradlew @@ -130,7 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/gradlew.bat b/gradlew.bat index a9f778a7a..ac1b06f93 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/sentry-android-core/build.gradle.kts b/sentry-android-core/build.gradle.kts index dbbc6948a..fb25b4332 100644 --- a/sentry-android-core/build.gradle.kts +++ b/sentry-android-core/build.gradle.kts @@ -72,7 +72,7 @@ android { } dependencies { - api(project(":sentry-core")) + api(project(":sentry")) // lifecycle processor, session tracking implementation(Config.Libs.lifecycleProcess) @@ -96,7 +96,7 @@ dependencies { testImplementation(Config.TestLibs.awaitility) } -//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() @@ -116,5 +116,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-android-core/proguard-rules.pro b/sentry-android-core/proguard-rules.pro index f96074f8e..c196022e4 100644 --- a/sentry-android-core/proguard-rules.pro +++ b/sentry-android-core/proguard-rules.pro @@ -11,7 +11,7 @@ -keep class com.google.gson.** { *; } # Application classes that will be serialized/deserialized over Gson --keep class io.sentry.core.** { *; } +-keep class io.sentry.** { *; } -keepclassmembers enum * { *; } -keep class io.sentry.android.core.** { *; } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ANRWatchDog.java b/sentry-android-core/src/main/java/io/sentry/android/core/ANRWatchDog.java index 6e10b00bd..b16087a05 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ANRWatchDog.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ANRWatchDog.java @@ -7,8 +7,8 @@ import android.app.ActivityManager; import android.content.Context; import android.os.Debug; -import io.sentry.core.ILogger; -import io.sentry.core.SentryLevel; +import io.sentry.ILogger; +import io.sentry.SentryLevel; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java index d3e438589..bb1274534 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java @@ -5,12 +5,12 @@ import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import io.sentry.core.Breadcrumb; -import io.sentry.core.IHub; -import io.sentry.core.Integration; -import io.sentry.core.SentryLevel; -import io.sentry.core.SentryOptions; -import io.sentry.core.util.Objects; +import io.sentry.Breadcrumb; +import io.sentry.IHub; +import io.sentry.Integration; +import io.sentry.SentryLevel; +import io.sentry.SentryOptions; +import io.sentry.util.Objects; import java.io.Closeable; import java.io.IOException; import org.jetbrains.annotations.NotNull; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLogger.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLogger.java index 972fc93e2..f1fd6f079 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLogger.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLogger.java @@ -1,8 +1,8 @@ package io.sentry.android.core; import android.util.Log; -import io.sentry.core.ILogger; -import io.sentry.core.SentryLevel; +import io.sentry.ILogger; +import io.sentry.SentryLevel; final class AndroidLogger implements ILogger { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java index 7454566ce..2069fa120 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java @@ -6,13 +6,13 @@ import android.content.Context; import android.content.pm.PackageInfo; import android.os.Build; -import io.sentry.core.ILogger; -import io.sentry.core.SendCachedEventFireAndForgetIntegration; -import io.sentry.core.SendFireAndForgetEnvelopeSender; -import io.sentry.core.SendFireAndForgetEventSender; -import io.sentry.core.SentryLevel; -import io.sentry.core.SentryOptions; -import io.sentry.core.util.Objects; +import io.sentry.ILogger; +import io.sentry.SendCachedEnvelopeFireAndForgetIntegration; +import io.sentry.SendFireAndForgetEnvelopeSender; +import io.sentry.SendFireAndForgetOutboxSender; +import io.sentry.SentryLevel; +import io.sentry.SentryOptions; +import io.sentry.util.Objects; import java.io.File; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -116,19 +116,15 @@ private static void installDefaultIntegrations( final @NotNull ILoadClass loadClass) { options.addIntegration( - new SendCachedEventFireAndForgetIntegration( - new SendFireAndForgetEventSender(() -> options.getCacheDirPath()))); - - options.addIntegration( - new SendCachedEventFireAndForgetIntegration( - new SendFireAndForgetEnvelopeSender(() -> options.getSessionsPath()))); + new SendCachedEnvelopeFireAndForgetIntegration( + new SendFireAndForgetEnvelopeSender(() -> options.getCacheDirPath()))); // Integrations are registered in the same order. NDK before adding Watch outbox, // because sentry-native move files around and we don't want to watch that. final Class sentryNdkClass = loadNdkIfAvailable(options, buildInfoProvider, loadClass); options.addIntegration(new NdkIntegration(sentryNdkClass)); - // this integration uses android.os.FileObserver, we can't move to sentry-core + // this integration uses android.os.FileObserver, we can't move to sentry // before creating a pure java impl. options.addIntegration(EnvelopeFileObserverIntegration.getOutboxFileObserver()); @@ -136,8 +132,8 @@ private static void installDefaultIntegrations( // this should be executed after NdkIntegration because sentry-native move files on init. // and we'd like to send them right away options.addIntegration( - new SendCachedEventFireAndForgetIntegration( - new SendFireAndForgetEnvelopeSender(() -> options.getOutboxPath()))); + new SendCachedEnvelopeFireAndForgetIntegration( + new SendFireAndForgetOutboxSender(() -> options.getOutboxPath()))); options.addIntegration(new AnrIntegration(context)); options.addIntegration(new AppLifecycleIntegration()); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransportGate.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransportGate.java index f0469c978..4b9f800c9 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransportGate.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransportGate.java @@ -1,9 +1,9 @@ package io.sentry.android.core; import android.content.Context; +import io.sentry.ILogger; import io.sentry.android.core.util.ConnectivityChecker; -import io.sentry.core.ILogger; -import io.sentry.core.transport.ITransportGate; +import io.sentry.transport.ITransportGate; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.TestOnly; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java index c83744d8c..d82689fd5 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java @@ -2,14 +2,14 @@ import android.annotation.SuppressLint; import android.content.Context; -import io.sentry.core.IHub; -import io.sentry.core.ILogger; -import io.sentry.core.Integration; -import io.sentry.core.SentryLevel; -import io.sentry.core.SentryOptions; -import io.sentry.core.exception.ExceptionMechanismException; -import io.sentry.core.protocol.Mechanism; -import io.sentry.core.util.Objects; +import io.sentry.IHub; +import io.sentry.ILogger; +import io.sentry.Integration; +import io.sentry.SentryLevel; +import io.sentry.SentryOptions; +import io.sentry.exception.ExceptionMechanismException; +import io.sentry.protocol.Mechanism; +import io.sentry.util.Objects; import java.io.Closeable; import java.io.IOException; import org.jetbrains.annotations.NotNull; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java index 53c45f88d..ce2b3b58a 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java @@ -5,14 +5,14 @@ import android.content.res.Configuration; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import io.sentry.Breadcrumb; +import io.sentry.IHub; +import io.sentry.Integration; +import io.sentry.SentryLevel; +import io.sentry.SentryOptions; import io.sentry.android.core.util.DeviceOrientations; -import io.sentry.core.Breadcrumb; -import io.sentry.core.IHub; -import io.sentry.core.Integration; -import io.sentry.core.SentryLevel; -import io.sentry.core.SentryOptions; -import io.sentry.core.protocol.Device; -import io.sentry.core.util.Objects; +import io.sentry.protocol.Device; +import io.sentry.util.Objects; import java.io.Closeable; import java.io.IOException; import java.util.Locale; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AppLifecycleIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AppLifecycleIntegration.java index 6f097bb71..ae163a8b6 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AppLifecycleIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AppLifecycleIntegration.java @@ -1,12 +1,12 @@ package io.sentry.android.core; import androidx.lifecycle.ProcessLifecycleOwner; +import io.sentry.IHub; +import io.sentry.Integration; +import io.sentry.SentryLevel; +import io.sentry.SentryOptions; import io.sentry.android.core.util.MainThreadChecker; -import io.sentry.core.IHub; -import io.sentry.core.Integration; -import io.sentry.core.SentryLevel; -import io.sentry.core.SentryOptions; -import io.sentry.core.util.Objects; +import io.sentry.util.Objects; import java.io.Closeable; import java.io.IOException; import org.jetbrains.annotations.NotNull; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationNotResponding.java b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationNotResponding.java index c346d4293..7299cd29d 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationNotResponding.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationNotResponding.java @@ -2,7 +2,7 @@ // Based on the class above. The API unnecessary here was removed. package io.sentry.android.core; -import io.sentry.core.util.Objects; +import io.sentry.util.Objects; import org.jetbrains.annotations.NotNull; /** diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java index cdd3dcdd6..ff4cd9d60 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java @@ -3,8 +3,8 @@ import android.content.Context; import android.content.pm.PackageInfo; import android.os.Build; -import io.sentry.core.ILogger; -import io.sentry.core.SentryLevel; +import io.sentry.ILogger; +import io.sentry.SentryLevel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java index 304afa861..e1564e859 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java @@ -18,24 +18,24 @@ import android.os.SystemClock; import android.provider.Settings; import android.util.DisplayMetrics; +import io.sentry.DateUtils; +import io.sentry.EventProcessor; +import io.sentry.ILogger; +import io.sentry.SentryEvent; +import io.sentry.SentryLevel; import io.sentry.android.core.util.ConnectivityChecker; import io.sentry.android.core.util.DeviceOrientations; import io.sentry.android.core.util.MainThreadChecker; import io.sentry.android.core.util.RootChecker; -import io.sentry.core.DateUtils; -import io.sentry.core.EventProcessor; -import io.sentry.core.ILogger; -import io.sentry.core.SentryEvent; -import io.sentry.core.SentryLevel; -import io.sentry.core.protocol.App; -import io.sentry.core.protocol.DebugImage; -import io.sentry.core.protocol.DebugMeta; -import io.sentry.core.protocol.Device; -import io.sentry.core.protocol.OperatingSystem; -import io.sentry.core.protocol.SentryThread; -import io.sentry.core.protocol.User; -import io.sentry.core.util.ApplyScopeUtils; -import io.sentry.core.util.Objects; +import io.sentry.protocol.App; +import io.sentry.protocol.DebugImage; +import io.sentry.protocol.DebugMeta; +import io.sentry.protocol.Device; +import io.sentry.protocol.OperatingSystem; +import io.sentry.protocol.SentryThread; +import io.sentry.protocol.User; +import io.sentry.util.ApplyScopeUtils; +import io.sentry.util.Objects; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserver.java b/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserver.java index 4061fdae9..b7b2d2abe 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserver.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserver.java @@ -1,17 +1,17 @@ package io.sentry.android.core; -import static io.sentry.core.SentryLevel.ERROR; +import static io.sentry.SentryLevel.ERROR; import android.os.FileObserver; -import io.sentry.core.IEnvelopeSender; -import io.sentry.core.ILogger; -import io.sentry.core.SentryLevel; -import io.sentry.core.hints.ApplyScopeData; -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.core.util.Objects; +import io.sentry.IEnvelopeSender; +import io.sentry.ILogger; +import io.sentry.SentryLevel; +import io.sentry.hints.ApplyScopeData; +import io.sentry.hints.Cached; +import io.sentry.hints.Flushable; +import io.sentry.hints.Retryable; +import io.sentry.hints.SubmissionResult; +import io.sentry.util.Objects; import java.io.File; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserverIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserverIntegration.java index dcfdcc6ed..2e6947da5 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserverIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserverIntegration.java @@ -1,12 +1,12 @@ package io.sentry.android.core; -import io.sentry.core.EnvelopeSender; -import io.sentry.core.IHub; -import io.sentry.core.ILogger; -import io.sentry.core.Integration; -import io.sentry.core.SentryLevel; -import io.sentry.core.SentryOptions; -import io.sentry.core.util.Objects; +import io.sentry.IHub; +import io.sentry.ILogger; +import io.sentry.Integration; +import io.sentry.OutboxSender; +import io.sentry.SentryLevel; +import io.sentry.SentryOptions; +import io.sentry.util.Objects; import java.io.Closeable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -37,8 +37,8 @@ public final void register(final @NotNull IHub hub, final @NotNull SentryOptions logger.log( SentryLevel.DEBUG, "Registering EnvelopeFileObserverIntegration for path: %s", path); - final EnvelopeSender envelopeSender = - new EnvelopeSender( + final OutboxSender outboxSender = + new OutboxSender( hub, options.getEnvelopeReader(), options.getSerializer(), @@ -46,7 +46,7 @@ public final void register(final @NotNull IHub hub, final @NotNull SentryOptions options.getFlushTimeoutMillis()); observer = - new EnvelopeFileObserver(path, envelopeSender, logger, options.getFlushTimeoutMillis()); + new EnvelopeFileObserver(path, outboxSender, logger, options.getFlushTimeoutMillis()); observer.startWatching(); logger.log(SentryLevel.DEBUG, "EnvelopeFileObserverIntegration installed."); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java b/sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java index a83ea1a44..b0a435f06 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java @@ -2,11 +2,11 @@ import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; -import io.sentry.core.Breadcrumb; -import io.sentry.core.IHub; -import io.sentry.core.SentryLevel; -import io.sentry.core.transport.CurrentDateProvider; -import io.sentry.core.transport.ICurrentDateProvider; +import io.sentry.Breadcrumb; +import io.sentry.IHub; +import io.sentry.SentryLevel; +import io.sentry.transport.CurrentDateProvider; +import io.sentry.transport.ICurrentDateProvider; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java index 11fad962a..e774219ac 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java @@ -4,9 +4,9 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Bundle; -import io.sentry.core.ILogger; -import io.sentry.core.SentryLevel; -import io.sentry.core.util.Objects; +import io.sentry.ILogger; +import io.sentry.SentryLevel; +import io.sentry.util.Objects; import java.util.Locale; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -84,6 +84,7 @@ static void applyMetadata( options.setEnableSessionTracking(sessionTrackingEnabled); if (options.getSampleRate() == null) { + // TODO: it needs to read a Float I guess Double sampleRate = metadata.getDouble(SAMPLE_RATE, -1); options.getLogger().log(SentryLevel.DEBUG, "sampleRate read: %s", sampleRate); if (sampleRate != -1) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/NdkIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/NdkIntegration.java index 5d0022e09..497192370 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/NdkIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/NdkIntegration.java @@ -1,10 +1,10 @@ package io.sentry.android.core; -import io.sentry.core.IHub; -import io.sentry.core.Integration; -import io.sentry.core.SentryLevel; -import io.sentry.core.SentryOptions; -import io.sentry.core.util.Objects; +import io.sentry.IHub; +import io.sentry.Integration; +import io.sentry.SentryLevel; +import io.sentry.SentryOptions; +import io.sentry.util.Objects; import java.lang.reflect.Method; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegration.java index bb8654e34..91a59f10e 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegration.java @@ -8,13 +8,13 @@ import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import androidx.annotation.Nullable; +import io.sentry.Breadcrumb; +import io.sentry.IHub; +import io.sentry.Integration; +import io.sentry.SentryLevel; +import io.sentry.SentryOptions; import io.sentry.android.core.util.Permissions; -import io.sentry.core.Breadcrumb; -import io.sentry.core.IHub; -import io.sentry.core.Integration; -import io.sentry.core.SentryLevel; -import io.sentry.core.SentryOptions; -import io.sentry.core.util.Objects; +import io.sentry.util.Objects; import java.io.Closeable; import java.io.IOException; import org.jetbrains.annotations.NotNull; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java index bebd25e60..0bf9fcb99 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java @@ -1,10 +1,10 @@ package io.sentry.android.core; import android.content.Context; -import io.sentry.core.ILogger; -import io.sentry.core.OptionsContainer; -import io.sentry.core.Sentry; -import io.sentry.core.SentryLevel; +import io.sentry.ILogger; +import io.sentry.OptionsContainer; +import io.sentry.Sentry; +import io.sentry.SentryLevel; import java.lang.reflect.InvocationTargetException; import org.jetbrains.annotations.NotNull; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java index 0c10ba972..9e98a78a7 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java @@ -1,7 +1,7 @@ package io.sentry.android.core; -import io.sentry.core.SentryOptions; -import io.sentry.core.protocol.SdkVersion; +import io.sentry.SentryOptions; +import io.sentry.protocol.SdkVersion; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryInitProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryInitProvider.java index 0a5317267..d4e5b30e5 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryInitProvider.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryInitProvider.java @@ -6,8 +6,8 @@ import android.content.pm.ProviderInfo; import android.database.Cursor; import android.net.Uri; -import io.sentry.core.Sentry; -import io.sentry.core.SentryLevel; +import io.sentry.Sentry; +import io.sentry.SentryLevel; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Internal diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java index 017cfc382..921ee5aa8 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java @@ -37,14 +37,14 @@ import android.content.IntentFilter; import android.os.Bundle; import androidx.annotation.Nullable; -import io.sentry.core.Breadcrumb; -import io.sentry.core.IHub; -import io.sentry.core.ILogger; -import io.sentry.core.Integration; -import io.sentry.core.SentryLevel; -import io.sentry.core.SentryOptions; -import io.sentry.core.util.Objects; -import io.sentry.core.util.StringUtils; +import io.sentry.Breadcrumb; +import io.sentry.IHub; +import io.sentry.ILogger; +import io.sentry.Integration; +import io.sentry.SentryLevel; +import io.sentry.SentryOptions; +import io.sentry.util.Objects; +import io.sentry.util.StringUtils; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java index c93991130..a91b0d69d 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java @@ -8,12 +8,12 @@ import android.hardware.SensorEventListener; import android.hardware.SensorManager; import androidx.annotation.Nullable; -import io.sentry.core.Breadcrumb; -import io.sentry.core.IHub; -import io.sentry.core.Integration; -import io.sentry.core.SentryLevel; -import io.sentry.core.SentryOptions; -import io.sentry.core.util.Objects; +import io.sentry.Breadcrumb; +import io.sentry.IHub; +import io.sentry.Integration; +import io.sentry.SentryLevel; +import io.sentry.SentryOptions; +import io.sentry.util.Objects; import java.io.Closeable; import java.io.IOException; import org.jetbrains.annotations.NotNull; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/util/ConnectivityChecker.java b/sentry-android-core/src/main/java/io/sentry/android/core/util/ConnectivityChecker.java index 02b23437c..a9f8e0fe8 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/util/ConnectivityChecker.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/util/ConnectivityChecker.java @@ -7,9 +7,9 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.os.Build; +import io.sentry.ILogger; +import io.sentry.SentryLevel; import io.sentry.android.core.IBuildInfoProvider; -import io.sentry.core.ILogger; -import io.sentry.core.SentryLevel; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/util/DeviceOrientations.java b/sentry-android-core/src/main/java/io/sentry/android/core/util/DeviceOrientations.java index 597c3d68a..638298b66 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/util/DeviceOrientations.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/util/DeviceOrientations.java @@ -1,7 +1,7 @@ package io.sentry.android.core.util; import android.content.res.Configuration; -import io.sentry.core.protocol.Device; +import io.sentry.protocol.Device; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/util/MainThreadChecker.java b/sentry-android-core/src/main/java/io/sentry/android/core/util/MainThreadChecker.java index ea0d9f17e..c6233af9c 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/util/MainThreadChecker.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/util/MainThreadChecker.java @@ -1,7 +1,7 @@ package io.sentry.android.core.util; import android.os.Looper; -import io.sentry.core.protocol.SentryThread; +import io.sentry.protocol.SentryThread; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/util/Permissions.java b/sentry-android-core/src/main/java/io/sentry/android/core/util/Permissions.java index 3fd2f0e5c..1df9bf390 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/util/Permissions.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/util/Permissions.java @@ -3,7 +3,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.Process; -import io.sentry.core.util.Objects; +import io.sentry.util.Objects; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Internal diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/util/RootChecker.java b/sentry-android-core/src/main/java/io/sentry/android/core/util/RootChecker.java index 321664203..7c2e4bfab 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/util/RootChecker.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/util/RootChecker.java @@ -2,10 +2,10 @@ import android.content.Context; import android.content.pm.PackageManager; +import io.sentry.ILogger; +import io.sentry.SentryLevel; import io.sentry.android.core.IBuildInfoProvider; -import io.sentry.core.ILogger; -import io.sentry.core.SentryLevel; -import io.sentry.core.util.Objects; +import io.sentry.util.Objects; import java.io.BufferedReader; import java.io.File; import java.io.IOException; diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityBreadcrumbsIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityBreadcrumbsIntegrationTest.kt index 2c2306b04..0d77c3e23 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityBreadcrumbsIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityBreadcrumbsIntegrationTest.kt @@ -6,9 +6,9 @@ import com.nhaarman.mockitokotlin2.check import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.never import com.nhaarman.mockitokotlin2.verify -import io.sentry.core.Breadcrumb -import io.sentry.core.IHub -import io.sentry.core.SentryLevel +import io.sentry.Breadcrumb +import io.sentry.IHub +import io.sentry.SentryLevel import kotlin.test.Test import kotlin.test.assertEquals diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt index e0c04602b..ef1fa0c5f 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt @@ -11,11 +11,11 @@ import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.never import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever -import io.sentry.core.ILogger -import io.sentry.core.MainEventProcessor -import io.sentry.core.SendCachedEventFireAndForgetIntegration -import io.sentry.core.SentryLevel -import io.sentry.core.SentryOptions +import io.sentry.ILogger +import io.sentry.MainEventProcessor +import io.sentry.SendCachedEnvelopeFireAndForgetIntegration +import io.sentry.SentryLevel +import io.sentry.SentryOptions import java.io.File import java.lang.RuntimeException import kotlin.test.BeforeTest @@ -92,16 +92,6 @@ class AndroidOptionsInitializerTest { assertTrue(sentryOptions.outboxPath?.endsWith("${File.separator}cache${File.separator}sentry${File.separator}outbox")!!) } - @Test - fun `sessionDir should be set at initialization`() { - val sentryOptions = SentryAndroidOptions() - val mockContext = createMockContext() - - AndroidOptionsInitializer.init(sentryOptions, mockContext) - - assertTrue(sentryOptions.sessionsPath?.endsWith("${File.separator}cache${File.separator}sentry${File.separator}sessions")!!) - } - @Test fun `init should set context package name as appInclude`() { val sentryOptions = SentryAndroidOptions() @@ -271,12 +261,12 @@ class AndroidOptionsInitializerTest { } @Test - fun `SendCachedEventFireAndForgetIntegration added to integration list`() { + fun `SendCachedEnvelopeFireAndForgetIntegration added to integration list`() { val sentryOptions = SentryAndroidOptions() val mockContext = createMockContext() AndroidOptionsInitializer.init(sentryOptions, mockContext) - val actual = sentryOptions.integrations.firstOrNull { it is SendCachedEventFireAndForgetIntegration } + val actual = sentryOptions.integrations.firstOrNull { it is SendCachedEnvelopeFireAndForgetIntegration } assertNotNull(actual) } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AnrIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AnrIntegrationTest.kt index c292c6423..14d55987a 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AnrIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AnrIntegrationTest.kt @@ -3,7 +3,7 @@ package io.sentry.android.core import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verify -import io.sentry.core.IHub +import io.sentry.IHub import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertNotNull diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt index 8bbe23106..0bda79e75 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt @@ -10,9 +10,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.Breadcrumb -import io.sentry.core.IHub -import io.sentry.core.SentryLevel +import io.sentry.Breadcrumb +import io.sentry.IHub +import io.sentry.SentryLevel import java.lang.NullPointerException import kotlin.test.Test import kotlin.test.assertEquals diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AppLifecycleIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AppLifecycleIntegrationTest.kt index 084f6268a..614962269 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AppLifecycleIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AppLifecycleIntegrationTest.kt @@ -4,7 +4,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verify -import io.sentry.core.IHub +import io.sentry.IHub import java.util.concurrent.CountDownLatch import kotlin.test.Test import kotlin.test.assertNotNull diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/CachedEvent.kt b/sentry-android-core/src/test/java/io/sentry/android/core/CachedEvent.kt index caee1a774..486404a14 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/CachedEvent.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/CachedEvent.kt @@ -1,5 +1,5 @@ package io.sentry.android.core -import io.sentry.core.hints.Cached +import io.sentry.hints.Cached class CachedEvent : Cached diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt index d227d9524..3a9961a46 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt @@ -9,18 +9,18 @@ import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.never import com.nhaarman.mockitokotlin2.verify +import io.sentry.DiagnosticLogger +import io.sentry.ILogger +import io.sentry.SentryEvent +import io.sentry.SentryLevel +import io.sentry.SentryOptions import io.sentry.android.core.DefaultAndroidEventProcessor.ANDROID_ID import io.sentry.android.core.DefaultAndroidEventProcessor.EMULATOR import io.sentry.android.core.DefaultAndroidEventProcessor.KERNEL_VERSION import io.sentry.android.core.DefaultAndroidEventProcessor.PROGUARD_UUID import io.sentry.android.core.DefaultAndroidEventProcessor.ROOTED -import io.sentry.core.DiagnosticLogger -import io.sentry.core.ILogger -import io.sentry.core.SentryEvent -import io.sentry.core.SentryLevel -import io.sentry.core.SentryOptions -import io.sentry.core.protocol.SdkVersion -import io.sentry.core.protocol.SentryThread +import io.sentry.protocol.SdkVersion +import io.sentry.protocol.SentryThread import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/EnvelopeFileObserverIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/EnvelopeFileObserverIntegrationTest.kt index 55d22aff7..2899c2c83 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/EnvelopeFileObserverIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/EnvelopeFileObserverIntegrationTest.kt @@ -3,9 +3,9 @@ package io.sentry.android.core import androidx.test.ext.junit.runners.AndroidJUnit4 import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verify -import io.sentry.core.Hub -// import io.sentry.core.HubAdapter -import io.sentry.core.SentryOptions +import io.sentry.Hub +// import io.sentry.HubAdapter +import io.sentry.SentryOptions import java.io.File import java.nio.file.Files import kotlin.test.AfterTest diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/EnvelopeFileObserverTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/EnvelopeFileObserverTest.kt index b28af8ef9..d0ecd315b 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/EnvelopeFileObserverTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/EnvelopeFileObserverTest.kt @@ -10,10 +10,10 @@ import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.never import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verifyZeroInteractions -import io.sentry.core.IEnvelopeSender -import io.sentry.core.ILogger -import io.sentry.core.SentryOptions -import io.sentry.core.hints.ApplyScopeData +import io.sentry.IEnvelopeSender +import io.sentry.ILogger +import io.sentry.SentryOptions +import io.sentry.hints.ApplyScopeData import java.io.File import kotlin.test.Test import kotlin.test.assertEquals diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/LifecycleWatcherTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/LifecycleWatcherTest.kt index 3b1091af8..58e7c77d0 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/LifecycleWatcherTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/LifecycleWatcherTest.kt @@ -8,10 +8,10 @@ import com.nhaarman.mockitokotlin2.never import com.nhaarman.mockitokotlin2.times import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever -import io.sentry.core.Breadcrumb -import io.sentry.core.IHub -import io.sentry.core.SentryLevel -import io.sentry.core.transport.ICurrentDateProvider +import io.sentry.Breadcrumb +import io.sentry.IHub +import io.sentry.SentryLevel +import io.sentry.transport.ICurrentDateProvider import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt index aebc78a1a..392d3a1ec 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt @@ -7,8 +7,8 @@ import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.never import com.nhaarman.mockitokotlin2.verify -import io.sentry.core.ILogger -import io.sentry.core.SentryLevel +import io.sentry.ILogger +import io.sentry.SentryLevel import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -97,13 +97,13 @@ class ManifestMetadataReaderTest { val options = SentryAndroidOptions() val bundle = Bundle() val mockContext = ContextUtilsTest.mockMetaData(metaData = bundle) - bundle.putBoolean(ManifestMetadataReader.SESSION_TRACKING_ENABLE, true) + bundle.putBoolean(ManifestMetadataReader.SESSION_TRACKING_ENABLE, false) // Act ManifestMetadataReader.applyMetadata(mockContext, options) // Assert - assertTrue(options.isEnableSessionTracking) + assertFalse(options.isEnableSessionTracking) } @Test @@ -117,7 +117,7 @@ class ManifestMetadataReaderTest { ManifestMetadataReader.applyMetadata(mockContext, options) // Assert - assertFalse(options.isEnableSessionTracking) + assertTrue(options.isEnableSessionTracking) } @Test diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/NdkIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/NdkIntegrationTest.kt index 3d9ed591f..717bfbc9c 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/NdkIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/NdkIntegrationTest.kt @@ -5,9 +5,9 @@ import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.never import com.nhaarman.mockitokotlin2.verify -import io.sentry.core.ILogger -import io.sentry.core.SentryLevel -import io.sentry.core.SentryOptions +import io.sentry.ILogger +import io.sentry.SentryLevel +import io.sentry.SentryOptions import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertTrue diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegrationTest.kt index 7bc7b61ee..0a7dcd8f5 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegrationTest.kt @@ -10,9 +10,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.Breadcrumb -import io.sentry.core.IHub -import io.sentry.core.SentryLevel +import io.sentry.Breadcrumb +import io.sentry.IHub +import io.sentry.SentryLevel import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt index c43297577..323b911a7 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt @@ -31,7 +31,7 @@ class SentryAndroidOptionsTest { }) assertTrue(sdkVersion.packages!!.any { - it.name == "maven:sentry-core" && + it.name == "maven:sentry" && it.version == BuildConfig.VERSION_NAME }) } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt index f0ec60762..ecf41c538 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt @@ -7,9 +7,9 @@ import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.never import com.nhaarman.mockitokotlin2.verify -import io.sentry.core.ILogger -import io.sentry.core.Sentry -import io.sentry.core.SentryLevel +import io.sentry.ILogger +import io.sentry.Sentry +import io.sentry.SentryLevel import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryInitProviderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryInitProviderTest.kt index f90b689ae..345d550d4 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryInitProviderTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryInitProviderTest.kt @@ -4,9 +4,9 @@ import android.content.pm.ProviderInfo import android.os.Bundle import androidx.test.ext.junit.runners.AndroidJUnit4 import com.nhaarman.mockitokotlin2.mock -import io.sentry.core.ILogger -import io.sentry.core.InvalidDsnException -import io.sentry.core.Sentry +import io.sentry.ILogger +import io.sentry.InvalidDsnException +import io.sentry.Sentry import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertFailsWith diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryNdk.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryNdk.kt index dca1cf09e..b112c8d27 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryNdk.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryNdk.kt @@ -1,6 +1,6 @@ package io.sentry.android.core -import io.sentry.core.SentryOptions +import io.sentry.SentryOptions class SentryNdk { companion object { diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegrationTest.kt index 894a941ae..81f6a7e2e 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegrationTest.kt @@ -7,9 +7,9 @@ import com.nhaarman.mockitokotlin2.check import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.never import com.nhaarman.mockitokotlin2.verify -import io.sentry.core.Breadcrumb -import io.sentry.core.IHub -import io.sentry.core.SentryLevel +import io.sentry.Breadcrumb +import io.sentry.IHub +import io.sentry.SentryLevel import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/TempSensorBreadcrumbsIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/TempSensorBreadcrumbsIntegrationTest.kt index 7fdddce9b..1d31a4077 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/TempSensorBreadcrumbsIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/TempSensorBreadcrumbsIntegrationTest.kt @@ -12,9 +12,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.Breadcrumb -import io.sentry.core.IHub -import io.sentry.core.SentryLevel +import io.sentry.Breadcrumb +import io.sentry.IHub +import io.sentry.SentryLevel import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/util/DeviceOrientationsTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/util/DeviceOrientationsTest.kt index b1a8130ba..f0148f67e 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/util/DeviceOrientationsTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/util/DeviceOrientationsTest.kt @@ -5,7 +5,7 @@ import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.content.res.Configuration.ORIENTATION_SQUARE import android.content.res.Configuration.ORIENTATION_UNDEFINED import io.sentry.android.core.util.DeviceOrientations.getOrientation -import io.sentry.core.protocol.Device +import io.sentry.protocol.Device import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNull diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/util/MainThreadCheckerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/util/MainThreadCheckerTest.kt index 95ba09e43..5619200c8 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/util/MainThreadCheckerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/util/MainThreadCheckerTest.kt @@ -1,7 +1,7 @@ package io.sentry.android.core.util import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.sentry.core.protocol.SentryThread +import io.sentry.protocol.SentryThread import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertTrue diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/util/RootCheckerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/util/RootCheckerTest.kt index 6474bf3b1..a17b19694 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/util/RootCheckerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/util/RootCheckerTest.kt @@ -8,8 +8,8 @@ import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever +import io.sentry.ILogger import io.sentry.android.core.IBuildInfoProvider -import io.sentry.core.ILogger import java.io.ByteArrayInputStream import java.io.File import java.io.IOException diff --git a/sentry-android-ndk/build.gradle.kts b/sentry-android-ndk/build.gradle.kts index d6ccf4b2c..f59a4cb7b 100644 --- a/sentry-android-ndk/build.gradle.kts +++ b/sentry-android-ndk/build.gradle.kts @@ -100,7 +100,7 @@ android { } dependencies { - api(project(":sentry-core")) + api(project(":sentry")) api(project(":sentry-android-core")) compileOnly(Config.CompileOnly.jetbrainsAnnotations) @@ -119,7 +119,7 @@ tasks.named("preBuild") { dependsOn(initNative) } -//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() @@ -139,5 +139,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-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdk.java b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdk.java index aaecb89f8..faf1e585a 100644 --- a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdk.java +++ b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdk.java @@ -1,6 +1,6 @@ package io.sentry.android.ndk; -import io.sentry.core.SentryOptions; +import io.sentry.SentryOptions; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; diff --git a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdkUtil.java b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdkUtil.java index 1b492aa04..e2686fe88 100644 --- a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdkUtil.java +++ b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdkUtil.java @@ -1,6 +1,6 @@ package io.sentry.android.ndk; -import io.sentry.core.protocol.SdkVersion; +import io.sentry.protocol.SdkVersion; import org.jetbrains.annotations.Nullable; /** diff --git a/sentry-android-ndk/src/test/java/io/sentry/android/ndk/SentryNdkUtilTest.kt b/sentry-android-ndk/src/test/java/io/sentry/android/ndk/SentryNdkUtilTest.kt index f861cc235..0082565cd 100644 --- a/sentry-android-ndk/src/test/java/io/sentry/android/ndk/SentryNdkUtilTest.kt +++ b/sentry-android-ndk/src/test/java/io/sentry/android/ndk/SentryNdkUtilTest.kt @@ -1,6 +1,6 @@ package io.sentry.android.ndk -import io.sentry.core.SentryOptions +import io.sentry.SentryOptions import kotlin.test.Test import kotlin.test.assertNull import kotlin.test.assertTrue diff --git a/sentry-android-timber/build.gradle.kts b/sentry-android-timber/build.gradle.kts index db948e00f..1bdc04f15 100644 --- a/sentry-android-timber/build.gradle.kts +++ b/sentry-android-timber/build.gradle.kts @@ -1,7 +1,7 @@ import com.novoda.gradle.release.PublishExtension -import org.jetbrains.kotlin.config.KotlinCompilerVersion import io.gitlab.arturbosch.detekt.Detekt import io.gitlab.arturbosch.detekt.extensions.DetektExtension +import org.jetbrains.kotlin.config.KotlinCompilerVersion plugins { id("com.android.library") @@ -67,7 +67,7 @@ android { } dependencies { - api(project(":sentry-core")) + api(project(":sentry")) api(Config.Libs.timber) @@ -80,7 +80,7 @@ dependencies { testImplementation(Config.TestLibs.mockitoInline) } -//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() @@ -100,7 +100,7 @@ configure { devEmail = Config.Sentry.devEmail scmConnection = Config.Sentry.scmConnection scmDevConnection = Config.Sentry.scmDevConnection - scmUrl = Config.Sentry.scmUrl + scmUrl = Config.Sentry.scmUrl } tasks.withType { diff --git a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt index 0ddb31e85..58571fcf4 100644 --- a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt +++ b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt @@ -1,11 +1,11 @@ package io.sentry.android.timber -import io.sentry.core.IHub -import io.sentry.core.ILogger -import io.sentry.core.Integration -import io.sentry.core.SentryLevel -import io.sentry.core.SentryOptions -import io.sentry.core.protocol.SdkVersion +import io.sentry.IHub +import io.sentry.ILogger +import io.sentry.Integration +import io.sentry.SentryLevel +import io.sentry.SentryOptions +import io.sentry.protocol.SdkVersion import java.io.Closeable import timber.log.Timber diff --git a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberTree.kt b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberTree.kt index 0a028e5ff..04e77610c 100644 --- a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberTree.kt +++ b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberTree.kt @@ -1,11 +1,11 @@ package io.sentry.android.timber import android.util.Log -import io.sentry.core.Breadcrumb -import io.sentry.core.IHub -import io.sentry.core.SentryEvent -import io.sentry.core.SentryLevel -import io.sentry.core.protocol.Message +import io.sentry.Breadcrumb +import io.sentry.IHub +import io.sentry.SentryEvent +import io.sentry.SentryLevel +import io.sentry.protocol.Message import timber.log.Timber /** diff --git a/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt b/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt index 3eb307216..cf6efee20 100644 --- a/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt +++ b/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt @@ -3,10 +3,10 @@ package io.sentry.android.timber import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verify -import io.sentry.core.IHub -import io.sentry.core.SentryLevel -import io.sentry.core.SentryOptions -import io.sentry.core.protocol.SdkVersion +import io.sentry.IHub +import io.sentry.SentryLevel +import io.sentry.SentryOptions +import io.sentry.protocol.SdkVersion import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberTreeTest.kt b/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberTreeTest.kt index c0954b577..ee6dbe92a 100644 --- a/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberTreeTest.kt +++ b/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberTreeTest.kt @@ -5,10 +5,10 @@ import com.nhaarman.mockitokotlin2.check import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.never import com.nhaarman.mockitokotlin2.verify -import io.sentry.core.Breadcrumb -import io.sentry.core.IHub -import io.sentry.core.SentryLevel -import io.sentry.core.getExc +import io.sentry.Breadcrumb +import io.sentry.IHub +import io.sentry.SentryLevel +import io.sentry.getExc import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/sentry-android-timber/src/test/java/io/sentry/core/SentryEventKtx.kt b/sentry-android-timber/src/test/java/io/sentry/core/SentryEventKtx.kt index 329092cfb..95d342f3b 100644 --- a/sentry-android-timber/src/test/java/io/sentry/core/SentryEventKtx.kt +++ b/sentry-android-timber/src/test/java/io/sentry/core/SentryEventKtx.kt @@ -1,4 +1,4 @@ -package io.sentry.core +package io.sentry /** * package-private hack. diff --git a/sentry-android/build.gradle.kts b/sentry-android/build.gradle.kts index b67f905ba..6e48eb1cc 100644 --- a/sentry-android/build.gradle.kts +++ b/sentry-android/build.gradle.kts @@ -35,7 +35,7 @@ dependencies { api(project(":sentry-android-ndk")) } -//TODO: move these blocks to parent gradle file, DRY +// TODO: move these blocks to parent gradle file, DRY configure { userOrg = Config.Sentry.userOrg groupId = project.group.toString() @@ -55,5 +55,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/SendFireAndForgetEventSender.java b/sentry-core/src/main/java/io/sentry/core/SendFireAndForgetEventSender.java deleted file mode 100644 index 8bdfc314c..000000000 --- a/sentry-core/src/main/java/io/sentry/core/SendFireAndForgetEventSender.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.sentry.core; - -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public final class SendFireAndForgetEventSender - implements SendCachedEventFireAndForgetIntegration.SendFireAndForgetFactory { - - private final @NotNull SendCachedEventFireAndForgetIntegration.SendFireAndForgetDirPath - sendFireAndForgetDirPath; - - public SendFireAndForgetEventSender( - final @NotNull SendCachedEventFireAndForgetIntegration.SendFireAndForgetDirPath - sendFireAndForgetDirPath) { - this.sendFireAndForgetDirPath = sendFireAndForgetDirPath; - } - - @Override - public SendCachedEventFireAndForgetIntegration.SendFireAndForget create( - final @NotNull IHub hub, final @NotNull SentryOptions options) { - final String dirPath = sendFireAndForgetDirPath.getDirPath(); - if (!hasValidPath(dirPath, options.getLogger())) { - options.getLogger().log(SentryLevel.ERROR, "No cache dir path is defined in options."); - return null; - } - - final SendCachedEvent sender = - new SendCachedEvent( - options.getSerializer(), hub, options.getLogger(), options.getFlushTimeoutMillis()); - - return processDir(sender, dirPath, options.getLogger()); - } -} diff --git a/sentry-core/src/main/java/io/sentry/core/cache/DiskCache.java b/sentry-core/src/main/java/io/sentry/core/cache/DiskCache.java deleted file mode 100644 index 2054c4f80..000000000 --- a/sentry-core/src/main/java/io/sentry/core/cache/DiskCache.java +++ /dev/null @@ -1,169 +0,0 @@ -package io.sentry.core.cache; - -import static io.sentry.core.SentryLevel.DEBUG; -import static io.sentry.core.SentryLevel.ERROR; -import static io.sentry.core.SentryLevel.WARNING; -import static java.lang.String.format; - -import io.sentry.core.ISerializer; -import io.sentry.core.SentryEvent; -import io.sentry.core.SentryLevel; -import io.sentry.core.SentryOptions; -import io.sentry.core.util.Objects; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -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.Iterator; -import java.util.List; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -/** - * A simple cache implementation storing the events to a disk, each event in a separater file in a - * configured directory. - */ -@ApiStatus.Internal -public final class DiskCache implements IEventCache { - /** File suffix added to all serialized event files. */ - public static final String FILE_SUFFIX = ".sentry-event"; - - @SuppressWarnings("CharsetObjectCanBeUsed") - private static final Charset UTF_8 = Charset.forName("UTF-8"); - - private final File directory; - private final int maxSize; - private final ISerializer serializer; - private final SentryOptions options; - - public DiskCache(SentryOptions options) { - Objects.requireNonNull(options.getCacheDirPath(), "Cache dir. path is required."); - this.directory = new File(options.getCacheDirPath()); - this.maxSize = options.getCacheDirSize(); - this.serializer = options.getSerializer(); - this.options = options; - } - - @Override - public void store(SentryEvent event) { - if (getNumberOfStoredEvents() >= maxSize) { - options - .getLogger() - .log( - SentryLevel.WARNING, - "Disk cache full (respecting maxSize). Not storing event {}", - event); - return; - } - - File eventFile = getEventFile(event); - if (eventFile.exists()) { - options - .getLogger() - .log( - WARNING, - "Not adding Event to offline storage because it already exists: %s", - eventFile.getAbsolutePath()); - return; - } else { - options - .getLogger() - .log(DEBUG, "Adding Event to offline storage: %s", eventFile.getAbsolutePath()); - } - - try (final OutputStream outputStream = new FileOutputStream(eventFile); - final Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream, UTF_8))) { - serializer.serialize(event, writer); - } catch (Exception e) { - options - .getLogger() - .log(ERROR, e, "Error writing Event to offline storage: %s", event.getEventId()); - } - } - - @Override - public void discard(SentryEvent event) { - File eventFile = getEventFile(event); - if (eventFile.exists()) { - options - .getLogger() - .log(DEBUG, "Discarding event from cache: %s", eventFile.getAbsolutePath()); - - if (!eventFile.delete()) { - options.getLogger().log(ERROR, "Failed to delete Event: %s", eventFile.getAbsolutePath()); - } - } else { - options.getLogger().log(DEBUG, "Event was not cached: %s", eventFile.getAbsolutePath()); - } - } - - private int getNumberOfStoredEvents() { - return allEventFiles().length; - } - - private boolean isDirectoryValid() { - if (!directory.isDirectory() || !directory.canWrite() || !directory.canRead()) { - options - .getLogger() - .log( - ERROR, - "The directory for caching Sentry events is inaccessible.: %s", - directory.getAbsolutePath()); - return false; - } - return true; - } - - private File getEventFile(SentryEvent event) { - return new File(directory.getAbsolutePath(), event.getEventId().toString() + FILE_SUFFIX); - } - - @Override - public @NotNull Iterator iterator() { - File[] allCachedEvents = allEventFiles(); - - List 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")