diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml
index 75d6dbb2..36a73f20 100644
--- a/.buildkite/pipeline.yml
+++ b/.buildkite/pipeline.yml
@@ -22,32 +22,6 @@ steps:
run: java-common
command: './gradlew check test'
- - label: ':docker: Mazerunner java8 tests batch 1'
- key: 'java-mazerunner-tests-1'
- depends_on: 'java-jvm-build'
- timeout_in_minutes: 30
- plugins:
- - docker-compose#v3.7.0:
- run: java8-mazerunner
- - artifacts#v1.9.0:
- download: "maven-repository.zip"
- command:
- - 'features/scripts/assemble-fixtures.sh'
- - 'bundle exec maze-runner --exclude=features/[^a-m].*.feature'
-
- - label: ':docker: Mazerunner java8 tests batch 2'
- key: 'java-mazerunner-tests-2'
- depends_on: 'java-jvm-build'
- timeout_in_minutes: 30
- plugins:
- - docker-compose#v3.7.0:
- run: java8-mazerunner
- - artifacts#v1.9.0:
- download: "maven-repository.zip"
- command:
- - 'features/scripts/assemble-fixtures.sh'
- - 'bundle exec maze-runner --exclude=features/[^n-z].*.feature'
-
- label: ':docker: Mazerunner java17 tests batch 1'
key: 'java-mazerunner-tests-3'
depends_on: 'java-jvm-build'
@@ -57,6 +31,7 @@ steps:
run: java17-mazerunner
- artifacts#v1.9.0:
download: "maven-repository.zip"
+ upload: "maze_output/maze_output.zip"
command:
- 'features/scripts/assemble-fixtures.sh'
- 'bundle exec maze-runner --exclude=features/[^a-m].*.feature'
@@ -70,6 +45,7 @@ steps:
run: java17-mazerunner
- artifacts#v1.9.0:
download: "maven-repository.zip"
+ upload: "maze_output/maze_output.zip"
command:
- 'features/scripts/assemble-fixtures.sh'
- 'bundle exec maze-runner --exclude=features/[^n-z].*.feature'
diff --git a/Gemfile b/Gemfile
index 85813553..1dcbd4b1 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
gem 'bugsnag-maze-runner', '~>9.0'
gem 'os'
+# Pin power_assert to avoid compatibility issues with test-unit
+gem 'power_assert', '< 3.0.0'
diff --git a/bugsnag-spring/build.gradle b/bugsnag-spring/build.gradle
deleted file mode 100644
index 379cc762..00000000
--- a/bugsnag-spring/build.gradle
+++ /dev/null
@@ -1,204 +0,0 @@
-ext {
- jakartaSpringVersion = '6.0.0'
- jakartaSpringBootVersion = '3.0.0'
-
- javaxSpringVersion = '5.3.20'
- javaxSpringBootVersion = '2.5.14'
-}
-
-apply plugin: 'java-library'
-
-repositories {
- mavenCentral()
-}
-
-java {
- withJavadocJar()
- toolchain {
- languageVersion.set(JavaLanguageVersion.of(8))
- }
-}
-
-// We use 3 custom configurations and matching sourceSets to avoid the standard behaviours which interfere with
-// our compilation structure
-configurations {
- compileCommon
- compileJavax
- compileJakarta
-}
-
-sourceSets {
- common {
- java
- // common.compileClasspath includes the compileJavax classpath in order to give the common classes access
- // to Spring without confusing the dependencies, including it here ensures that any use of javax.* packages
- // in src/common will result in test failures (although not compile failures)
- // we need the javax.* packages included here so that the compiler can type-check all the way up the hierarchy
- // for things like "ServletRequestBindingException extends ServletException"
- compileClasspath += configurations.compileCommon + configurations.compileJavax
- }
- javax {
- java
- compileClasspath += configurations.compileJavax + common.output + configurations.compileCommon
- }
- jakarta {
- java
- compileClasspath += configurations.compileJakarta + common.output + configurations.compileCommon
- }
-}
-
-// test sourceSets
-sourceSets {
- commonTest {
- java
- }
- javaxTest {
- java
- compileClasspath += javax.output + javax.compileClasspath + commonTest.output + commonTest.runtimeClasspath + common.output
- runtimeClasspath = compileClasspath
- }
- jakartaTest {
- java
- compileClasspath += jakarta.output + jakarta.compileClasspath + commonTest.output + commonTest.runtimeClasspath + common.output
- runtimeClasspath = compileClasspath
- }
-}
-
-tasks.register('sourceJar', Jar).configure {
- from(
- sourceSets.common.allJava,
- sourceSets.javax.allJava,
- sourceSets.jakarta.allJava
- )
-}
-
-// do not move this higher - sourceJar must be registered before we apply common.gradle
-apply from: '../common.gradle'
-
-compileJava.dependsOn(compileCommonJava, compileJavaxJava, compileJakartaJava)
-
-// Separated Javax / Jakarta tests -------------------------------------------------------------------------------------
-
-compileJakartaJava {
- // set the compiler for the `jakarta` sourceSet to Java17
- javaCompiler.set(
- javaToolchains.compilerFor {
- languageVersion = JavaLanguageVersion.of(17)
- }
- )
-}
-
-compileJakartaTestJava {
- // set the compiler for the `jakartaTest` sourceSet to Java17
- javaCompiler.set(
- javaToolchains.compilerFor {
- languageVersion = JavaLanguageVersion.of(17)
- }
- )
-}
-
-testClasses.dependsOn(javaxTestClasses, jakartaTestClasses)
-
-tasks.register('testJakarta', Test) {
- testClassesDirs = sourceSets.jakartaTest.output.classesDirs
- classpath = sourceSets.jakartaTest.output.classesDirs + sourceSets.jakartaTest.runtimeClasspath
- javaLauncher.set(
- javaToolchains.launcherFor {
- languageVersion = JavaLanguageVersion.of(17)
- }
- )
-
- dependsOn(jakartaTestClasses)
-}
-
-tasks.register('testJavax', Test) {
- testClassesDirs = sourceSets.javaxTest.output.classesDirs
- classpath = sourceSets.javaxTest.output.classesDirs + sourceSets.javaxTest.runtimeClasspath
- javaLauncher.set(
- javaToolchains.launcherFor {
- languageVersion = JavaLanguageVersion.of(8)
- }
- )
-
- dependsOn(javaxTestClasses)
-}
-
-test.dependsOn(testJakarta, testJavax)
-
-dependencies {
- compileCommon project(':bugsnag')
-
- compileCommon "ch.qos.logback:logback-core:${logbackVersion}"
- compileCommon "org.slf4j:slf4j-api:${slf4jApiVersion}"
-
- compileJavax "javax.servlet:javax.servlet-api:${javaxServletApiVersion}"
- compileJavax "org.springframework:spring-webmvc:${javaxSpringVersion}"
- compileJavax "org.springframework.boot:spring-boot:${javaxSpringBootVersion}"
- compileJavax "org.springframework:spring-aop:${javaxSpringVersion}"
-
- compileJakarta "jakarta.servlet:jakarta.servlet-api:${jakartaServletApiVersion}"
- compileJakarta "org.springframework:spring-webmvc:${jakartaSpringVersion}"
- compileJakarta "org.springframework.boot:spring-boot:${jakartaSpringBootVersion}"
- compileJakarta "org.springframework:spring-aop:${jakartaSpringVersion}"
-
-
- commonTestImplementation project(':bugsnag').sourceSets.test.output
- commonTestImplementation project(':bugsnag')
-
- commonTestImplementation "junit:junit:${junitVersion}"
-
- commonTestCompileOnly "org.mockito:mockito-core:2.10.0"
-
- jakartaTestImplementation "org.mockito:mockito-core:${mockitoVersion}"
- jakartaTestImplementation "jakarta.servlet:jakarta.servlet-api:${jakartaServletApiVersion}"
- jakartaTestImplementation "org.springframework.boot:spring-boot-starter-test:${jakartaSpringBootVersion}"
- jakartaTestImplementation "org.springframework.boot:spring-boot-starter-web:${jakartaSpringBootVersion}"
- jakartaTestImplementation "org.springframework:spring-aop:${jakartaSpringVersion}"
-
- javaxTestImplementation "org.mockito:mockito-core:2.10.0"
- javaxTestImplementation "javax.servlet:javax.servlet-api:${javaxServletApiVersion}"
- javaxTestImplementation "org.springframework.boot:spring-boot-starter-test:${javaxSpringBootVersion}"
- javaxTestImplementation "org.springframework.boot:spring-boot-starter-web:${javaxSpringBootVersion}"
- javaxTestImplementation "org.springframework:spring-aop:${javaxSpringVersion}"
-}
-
-dependencies {
- // ensure that the published .pom file includes a dependency on com.bugsnag:bugsnag
- // this 'api' dependency has no impact on the compilation of this projects source,
- // so we keep it in it's own dependencies block
- api project(':bugsnag')
-}
-
-// here is where we merge all of the class outputs into the default classes directory doing this as its own step avoids
-// circular dependencies between the sourceSets and their output directories (if they all use 'main.output.classesDirs'
-// then jakarta+javax both depend on it as well)
-tasks.register('mergeClasses', Copy) {
- from sourceSets.common.output.classesDirs
- from sourceSets.jakarta.output.classesDirs
- from sourceSets.javax.output.classesDirs
-
- into sourceSets.main.output.classesDirs.asPath
-}
-
-classes.dependsOn('mergeClasses')
-
-if (project.hasProperty('releasing')) {
- publishing {
- publications {
- Publication(MavenPublication) {
- // this is vital: we use the version details from the "javax" side of the project
- // this ensures that Gradle considers that as the baseline set of required versions
- // allowing old projects that still use Java8 and the javax.servlet packages to use
- // bugsnag-java without any secondary artifacts
- versionMapping {
- usage('java-api') {
- fromResolutionOf('compileCommon')
- }
- usage('java-runtime') {
- fromResolutionOf('compileCommon')
- }
- }
- }
- }
- }
-}
diff --git a/bugsnag-spring/build.gradle.kts b/bugsnag-spring/build.gradle.kts
new file mode 100644
index 00000000..89b66137
--- /dev/null
+++ b/bugsnag-spring/build.gradle.kts
@@ -0,0 +1,50 @@
+plugins {
+ `java-library`
+}
+
+repositories {
+ mavenCentral()
+}
+
+java {
+ withJavadocJar()
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(17))
+ }
+}
+
+val sourceJar by tasks.registering(Jar::class) {
+ from(sourceSets.main.get().allJava)
+}
+
+// do not move this higher - sourceJar must be registered before we apply common.gradle.kts
+apply(from = "../common.gradle.kts")
+
+dependencies {
+ implementation(project(":bugsnag"))
+
+ implementation(libs.logback.core)
+ implementation(libs.slf4j.api)
+
+ implementation(libs.jakarta.servlet.api)
+ implementation(libs.spring.webmvc)
+ implementation(libs.springBoot3.boot)
+ implementation(libs.spring.aop)
+
+ testImplementation(project(":bugsnag").dependencyProject.sourceSets["test"].output)
+ testImplementation(project(":bugsnag"))
+ testImplementation(libs.junit)
+ testImplementation(libs.springBoot3.starter.test)
+ testImplementation(libs.springBoot3.starter.web)
+ testImplementation(libs.junit.jupiter)
+ testCompileOnly(libs.mockito.core.legacy)
+}
+
+/** ---- Publishing config ----
+ * Pulls in publishing+signing rules from the shared release.gradle.kts.
+ * This will create tasks like:
+ * :bugsnag:publishMavenJavaPublicationToTestRepository
+ * :bugsnag:publishMavenJavaPublicationToOssrhStagingRepository
+ */
+apply(from = "${rootProject.projectDir}/release.gradle.kts")
+
diff --git a/bugsnag-spring/javax/build.gradle b/bugsnag-spring/javax/build.gradle
deleted file mode 100644
index edb7f763..00000000
--- a/bugsnag-spring/javax/build.gradle
+++ /dev/null
@@ -1,41 +0,0 @@
-ext {
- springVersion = '5.3.20'
- springBootVersion = '2.5.14'
-}
-
-apply plugin: 'java'
-apply plugin: 'java-library'
-
-apply from: '../../common.gradle'
-
-java {
- toolchain {
- languageVersion = JavaLanguageVersion.of(8)
- }
-}
-
-compileJava {
- sourceCompatibility = '1.8'
- targetCompatibility = '1.8'
-}
-
-repositories {
- mavenCentral()
-}
-
-dependencies {
- api project(':bugsnag')
- testImplementation project(':bugsnag').sourceSets.test.output
-
- compileOnly "javax.servlet:javax.servlet-api:${javaxServletApiVersion}"
- compileOnly "org.springframework:spring-webmvc:${springVersion}"
- compileOnly "org.springframework.boot:spring-boot:${springBootVersion}"
- compileOnly "ch.qos.logback:logback-core:${logbackVersion}"
- compileOnly "org.slf4j:slf4j-api:${slf4jApiVersion}"
-
- testImplementation "junit:junit:${junitVersion}"
- testImplementation "javax.servlet:javax.servlet-api:${javaxServletApiVersion}"
- testImplementation "org.springframework.boot:spring-boot-starter-test:${springBootVersion}"
- testImplementation "org.springframework.boot:spring-boot-starter-web:${springBootVersion}"
- testImplementation "org.mockito:mockito-core:${mockitoVersion}"
-}
\ No newline at end of file
diff --git a/bugsnag-spring/javax/src/test/resources/logback.xml b/bugsnag-spring/javax/src/test/resources/logback.xml
deleted file mode 100644
index 073f1525..00000000
--- a/bugsnag-spring/javax/src/test/resources/logback.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
- apiKey
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/bugsnag-spring/src/common/java/com/bugsnag/BugsnagImportSelector.java b/bugsnag-spring/src/common/java/com/bugsnag/BugsnagImportSelector.java
deleted file mode 100644
index 36f10e1a..00000000
--- a/bugsnag-spring/src/common/java/com/bugsnag/BugsnagImportSelector.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.bugsnag;
-
-import org.springframework.context.annotation.ImportSelector;
-import org.springframework.core.SpringVersion;
-import org.springframework.core.type.AnnotationMetadata;
-
-public class BugsnagImportSelector implements ImportSelector {
-
- private static final String[] SPRING_JAKARTA_CLASSES = {
- "com.bugsnag.SpringBootJakartaConfiguration",
- "com.bugsnag.JakartaMvcConfiguration",
- "com.bugsnag.ScheduledTaskConfiguration"
- };
-
- private static final String[] SPRING_JAVAX_CLASSES = {
- "com.bugsnag.SpringBootJavaxConfiguration",
- "com.bugsnag.JavaxMvcConfiguration",
- "com.bugsnag.ScheduledTaskConfiguration"
- };
-
- @Override
- public String[] selectImports(AnnotationMetadata importingClassMetadata) {
- if (isSpringJakartaCompatible() && isJava17Compatible()) {
- return SPRING_JAKARTA_CLASSES;
- }
-
- return SPRING_JAVAX_CLASSES;
- }
-
- private static boolean isSpringJakartaCompatible() {
- return getMajorVersion(SpringVersion.getVersion()) >= 6;
- }
-
- private static boolean isJava17Compatible() {
- return getMajorVersion(System.getProperty("java.version")) >= 17;
- }
-
- private static int getMajorVersion(String version) {
- if (version == null) {
- return 0;
- }
- int firstDot = version.indexOf(".");
- String majorVersion;
-
- if (firstDot == -1) {
- majorVersion = version;
- } else {
- majorVersion = version.substring(0, firstDot);
- }
-
- try {
- return Integer.parseInt(majorVersion);
- } catch (NumberFormatException nfe) {
- return 0;
- }
- }
-}
diff --git a/bugsnag-spring/src/common/java/com/bugsnag/ScheduledTaskConfiguration.java b/bugsnag-spring/src/common/java/com/bugsnag/ScheduledTaskConfiguration.java
deleted file mode 100644
index 6c9718a9..00000000
--- a/bugsnag-spring/src/common/java/com/bugsnag/ScheduledTaskConfiguration.java
+++ /dev/null
@@ -1,119 +0,0 @@
-package com.bugsnag;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.aop.framework.AopProxyUtils;
-import org.springframework.aop.support.AopUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.scheduling.TaskScheduler;
-import org.springframework.scheduling.annotation.SchedulingConfigurer;
-import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
-import org.springframework.scheduling.config.ScheduledTaskRegistrar;
-
-import org.springframework.util.ErrorHandler;
-
-import java.lang.reflect.Field;
-import java.util.concurrent.ScheduledExecutorService;
-
-/**
- * Add configuration for reporting unhandled exceptions for scheduled tasks.
- */
-@Configuration
-class ScheduledTaskConfiguration implements SchedulingConfigurer {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTaskConfiguration.class);
-
- @Autowired
- private Bugsnag bugsnag;
-
- @Autowired
- private ScheduledTaskBeanLocator beanLocator;
-
- /**
- * Add bugsnag error handling to a task scheduler
- */
- @Override
- public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
- BugsnagScheduledTaskExceptionHandler bugsnagErrorHandler =
- new BugsnagScheduledTaskExceptionHandler(bugsnag);
-
- // Decision process for finding a TaskScheduler, in order of preference:
- //
- // 1. use the scheduler from the task registrar
- // 2. search for a TaskScheduler bean, by type, then by name
- // 3. search for a ScheduledExecutorService bean by type, then by name,
- // and wrap it in a TaskScheduler
- // 4. create our own TaskScheduler
-
- TaskScheduler registrarScheduler = taskRegistrar.getScheduler();
- TaskScheduler taskScheduler = registrarScheduler != null
- ? registrarScheduler : beanLocator.resolveTaskScheduler();
-
- if (taskScheduler != null) {
- //check if taskSchedular is a proxy
- if (AopUtils.isAopProxy(taskScheduler)) {
- //if it's a proxy then get the target class and cast as necessary
- Class> targetClass = AopProxyUtils.ultimateTargetClass(taskScheduler);
- if (TaskScheduler.class.isAssignableFrom(targetClass)) {
- taskScheduler = (TaskScheduler) AopProxyUtils.getSingletonTarget(taskScheduler);
- }
- }
- configureExistingTaskScheduler(taskScheduler, bugsnagErrorHandler);
- } else {
- ScheduledExecutorService executorService = beanLocator.resolveScheduledExecutorService();
- taskScheduler = createNewTaskScheduler(executorService, bugsnagErrorHandler);
- taskRegistrar.setScheduler(taskScheduler);
- }
- }
-
- private TaskScheduler createNewTaskScheduler(
- ScheduledExecutorService executorService,
- BugsnagScheduledTaskExceptionHandler errorHandler) {
- if (executorService != null) {
- // create a task scheduler which delegates to the existing Executor
- ConcurrentTaskScheduler scheduler = new ConcurrentTaskScheduler(executorService);
- scheduler.setErrorHandler(errorHandler);
- return scheduler;
- } else {
- // If no task scheduler has been defined by the application, create one
- // and add the bugsnag error handler.
- ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
- scheduler.setErrorHandler(errorHandler);
- scheduler.initialize();
- return scheduler;
- }
- }
-
- /**
- * If a task scheduler has been defined by the application, get it so that
- * bugsnag error handling can be added.
- *
- * Reflection is the simplest way to get and set an error handler
- * because the error handler setter is only defined in the concrete classes,
- * not the TaskScheduler interface.
- *
- * @param taskScheduler the task scheduler
- */
- private void configureExistingTaskScheduler(TaskScheduler taskScheduler,
- BugsnagScheduledTaskExceptionHandler errorHandler) {
- try {
- Field errorHandlerField =
- taskScheduler.getClass().getDeclaredField("errorHandler");
- errorHandlerField.setAccessible(true);
- Object existingErrorHandler = errorHandlerField.get(taskScheduler);
-
- // If an error handler has already been defined then make the Bugsnag handler
- // call this afterwards
- if (existingErrorHandler instanceof ErrorHandler) {
- errorHandler.setExistingErrorHandler((ErrorHandler) existingErrorHandler);
- }
-
- // Add the bugsnag error handler to the scheduler.
- errorHandlerField.set(taskScheduler, errorHandler);
- } catch (Throwable ex) {
- LOGGER.warn("Bugsnag scheduled task exception handler could not be configured");
- }
- }
-}
diff --git a/bugsnag-spring/src/javax/java/com/bugsnag/BugsnagJavaxMvcExceptionHandler.java b/bugsnag-spring/src/javax/java/com/bugsnag/BugsnagJavaxMvcExceptionHandler.java
deleted file mode 100644
index 62cbf9ea..00000000
--- a/bugsnag-spring/src/javax/java/com/bugsnag/BugsnagJavaxMvcExceptionHandler.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.bugsnag;
-
-import com.bugsnag.HandledState.SeverityReasonType;
-
-import org.springframework.core.Ordered;
-import org.springframework.core.annotation.Order;
-import org.springframework.web.servlet.HandlerExceptionResolver;
-import org.springframework.web.servlet.ModelAndView;
-
-import java.util.Collections;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- * Reports uncaught exceptions thrown from handler mapping or execution to Bugsnag
- * and then passes the exception to the next handler in the chain.
- *
- * Set to highest precedence so that it should be called before other exception
- * resolvers.
- */
-@Order(Ordered.HIGHEST_PRECEDENCE)
-class BugsnagJavaxMvcExceptionHandler implements HandlerExceptionResolver {
-
- private final Bugsnag bugsnag;
-
- BugsnagJavaxMvcExceptionHandler(final Bugsnag bugsnag) {
- this.bugsnag = bugsnag;
- }
-
- @Override
- public ModelAndView resolveException(HttpServletRequest request,
- HttpServletResponse response,
- Object handler,
- java.lang.Exception ex) {
-
- if (bugsnag.getConfig().shouldSendUncaughtExceptions()) {
- HandledState handledState = HandledState.newInstance(
- SeverityReasonType.REASON_UNHANDLED_EXCEPTION_MIDDLEWARE,
- Collections.singletonMap("framework", "Spring"),
- Severity.ERROR,
- true);
-
- bugsnag.notify(ex, handledState, Thread.currentThread());
- }
-
- // Returning null passes the exception onto the next resolver in the chain.
- return null;
- }
-}
diff --git a/bugsnag-spring/src/javax/java/com/bugsnag/JavaxMvcConfiguration.java b/bugsnag-spring/src/javax/java/com/bugsnag/JavaxMvcConfiguration.java
deleted file mode 100644
index 8ba8e5e8..00000000
--- a/bugsnag-spring/src/javax/java/com/bugsnag/JavaxMvcConfiguration.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.bugsnag;
-
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Conditional;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * If spring-webmvc is loaded, add configuration for reporting unhandled exceptions.
- */
-@Configuration
-@Conditional(SpringWebMvcLoadedCondition.class)
-class JavaxMvcConfiguration implements InitializingBean {
-
- @Autowired
- private Bugsnag bugsnag;
-
- /**
- * Register an exception resolver to send unhandled reports to Bugsnag
- * for uncaught exceptions thrown from request handlers.
- */
- @Bean
- BugsnagJavaxMvcExceptionHandler bugsnagHandlerExceptionResolver() {
- return new BugsnagJavaxMvcExceptionHandler(bugsnag);
- }
-
- /**
- * Add a callback to assign specified severities for some Spring exceptions.
- */
- @Override
- public void afterPropertiesSet() {
- bugsnag.addCallback(new ExceptionClassCallback());
- }
-}
diff --git a/bugsnag-spring/src/javax/java/com/bugsnag/SpringBootJavaxConfiguration.java b/bugsnag-spring/src/javax/java/com/bugsnag/SpringBootJavaxConfiguration.java
deleted file mode 100644
index 1f63c81d..00000000
--- a/bugsnag-spring/src/javax/java/com/bugsnag/SpringBootJavaxConfiguration.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.bugsnag;
-
-import com.bugsnag.servlet.javax.BugsnagServletRequestListener;
-
-import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Conditional;
-import org.springframework.context.annotation.Configuration;
-
-import javax.servlet.ServletRequestListener;
-
-/**
- * If spring-boot is loaded, add configuration specific to Spring Boot
- */
-@Configuration
-@Conditional(SpringBootLoadedCondition.class)
-class SpringBootJavaxConfiguration extends SpringBootConfiguration {
-
- /**
- * The {@link com.bugsnag.servlet.javax.BugsnagServletContainerInitializer} does not work for Spring Boot, need to
- * register the {@link BugsnagServletRequestListener} using a Spring Boot
- * {@link ServletListenerRegistrationBean} instead. This adds session tracking and
- * automatic servlet request metadata collection.
- */
- @Bean
- @Conditional(SpringWebMvcLoadedCondition.class)
- ServletListenerRegistrationBean listenerRegistrationBean() {
- ServletListenerRegistrationBean srb =
- new ServletListenerRegistrationBean();
- srb.setListener(new BugsnagServletRequestListener());
- return srb;
- }
-}
diff --git a/bugsnag-spring/src/javaxTest/java/com/bugsnag/ScheduledTaskBeanLocatorTest.java b/bugsnag-spring/src/javaxTest/java/com/bugsnag/ScheduledTaskBeanLocatorTest.java
deleted file mode 100644
index 74eb856d..00000000
--- a/bugsnag-spring/src/javaxTest/java/com/bugsnag/ScheduledTaskBeanLocatorTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.bugsnag;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.when;
-
-import com.bugsnag.testapp.springboot.TestSpringBootApplication;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.context.ApplicationContext;
-import org.springframework.scheduling.TaskScheduler;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
-import org.springframework.test.context.junit4.SpringRunner;
-
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest(classes = TestSpringBootApplication.class)
-public class ScheduledTaskBeanLocatorTest {
-
- @Autowired
- private ScheduledTaskBeanLocator beanLocator;
-
- @MockBean
- private ApplicationContext context;
-
- @Before
- public void setUp() {
- beanLocator.setApplicationContext(context);
- }
-
- @Test
- public void findSchedulerByType() {
- ThreadPoolTaskScheduler expected = new ThreadPoolTaskScheduler();
- when(context.getBean(TaskScheduler.class)).thenReturn(expected);
- assertEquals(expected, beanLocator.resolveTaskScheduler());
- }
-
- @Test
- public void findSchedulerByName() {
- ThreadPoolTaskScheduler expected = new ThreadPoolTaskScheduler();
- Throwable exc = new NoUniqueBeanDefinitionException(TaskScheduler.class);
- when(context.getBean(TaskScheduler.class)).thenThrow(exc);
- when(context.getBean("taskScheduler", TaskScheduler.class)).thenReturn(expected);
- assertEquals(expected, beanLocator.resolveTaskScheduler());
- }
-
- @Test
- public void noTaskSchedulerAvailable() {
- assertNull(beanLocator.resolveTaskScheduler());
- }
-
- @Test
- public void findExecutorByType() {
- ScheduledExecutorService expected = Executors.newScheduledThreadPool(1);
- when(context.getBean(ScheduledExecutorService.class)).thenReturn(expected);
- assertEquals(expected, beanLocator.resolveScheduledExecutorService());
- }
-
- @Test
- public void findExecutorByName() {
- ScheduledExecutorService expected = Executors.newScheduledThreadPool(4);
- Throwable exc = new NoUniqueBeanDefinitionException(ScheduledExecutorService.class);
- when(context.getBean(ScheduledExecutorService.class)).thenThrow(exc);
- when(context.getBean("taskScheduler", ScheduledExecutorService.class))
- .thenReturn(expected);
- assertEquals(expected, beanLocator.resolveScheduledExecutorService());
- }
-
- @Test
- public void noScheduledExecutorAvailable() {
- assertNull(beanLocator.resolveScheduledExecutorService());
- }
-}
diff --git a/bugsnag-spring/src/javaxTest/java/com/bugsnag/ScheduledTaskConfigurationTest.java b/bugsnag-spring/src/javaxTest/java/com/bugsnag/ScheduledTaskConfigurationTest.java
deleted file mode 100644
index f619795d..00000000
--- a/bugsnag-spring/src/javaxTest/java/com/bugsnag/ScheduledTaskConfigurationTest.java
+++ /dev/null
@@ -1,148 +0,0 @@
-package com.bugsnag;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.mockito.Mockito.when;
-
-import com.bugsnag.testapp.springboot.TestSpringBootApplication;
-
-import org.aopalliance.intercept.MethodInterceptor;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.springframework.aop.framework.ProxyFactory;
-import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.context.ApplicationContext;
-import org.springframework.scheduling.TaskScheduler;
-import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
-import org.springframework.scheduling.config.ScheduledTaskRegistrar;
-import org.springframework.test.context.junit4.SpringRunner;
-
-import java.lang.reflect.Field;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest(classes = TestSpringBootApplication.class)
-public class ScheduledTaskConfigurationTest {
-
- @Autowired
- private ScheduledTaskConfiguration configuration;
-
- @Mock
- private ScheduledTaskRegistrar registrar;
-
- @Autowired
- private ScheduledTaskBeanLocator beanLocator;
-
- @MockBean
- private ApplicationContext context;
-
- @Before
- public void setUp() {
- registrar = new ScheduledTaskRegistrar();
- beanLocator.setApplicationContext(context);
- }
-
- @Test
- public void existingSchedulerUsed() {
- ThreadPoolTaskScheduler expected = new ThreadPoolTaskScheduler();
- registrar.setScheduler(expected);
- configuration.configureTasks(registrar);
- assertEquals(expected, registrar.getScheduler());
- }
-
- @Test
- public void noSchedulersAvailable() {
- configuration.configureTasks(registrar);
- assertTrue(registrar.getScheduler() instanceof ThreadPoolTaskScheduler);
- }
-
- @Test
- public void findSchedulerByType() throws NoSuchFieldException, IllegalAccessException {
- ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
- when(context.getBean(TaskScheduler.class)).thenReturn(scheduler);
-
- configuration.configureTasks(registrar);
- assertNull(registrar.getScheduler());
- Object errorHandler = accessField(scheduler, "errorHandler");
- assertTrue(errorHandler instanceof BugsnagScheduledTaskExceptionHandler);
- }
-
- @Test
- public void findSchedulerByName() throws NoSuchFieldException, IllegalAccessException {
- ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
- Throwable exc = new NoUniqueBeanDefinitionException(TaskScheduler.class);
- when(context.getBean(TaskScheduler.class)).thenThrow(exc);
- when(context.getBean("taskScheduler", TaskScheduler.class)).thenReturn(scheduler);
-
- configuration.configureTasks(registrar);
- assertNull(registrar.getScheduler());
- Object errorHandler = accessField(scheduler, "errorHandler");
- assertTrue(errorHandler instanceof BugsnagScheduledTaskExceptionHandler);
- }
-
- @Test
- public void findExecutorByType() throws NoSuchFieldException, IllegalAccessException {
- ScheduledExecutorService expected = Executors.newScheduledThreadPool(1);
- when(context.getBean(ScheduledExecutorService.class)).thenReturn(expected);
-
- configuration.configureTasks(registrar);
- TaskScheduler scheduler = registrar.getScheduler();
- assertTrue(scheduler instanceof ConcurrentTaskScheduler);
- assertEquals(expected, accessField(scheduler, "scheduledExecutor"));
- }
-
- @Test
- public void findExecutorByName() throws NoSuchFieldException, IllegalAccessException {
- ScheduledExecutorService expected = Executors.newScheduledThreadPool(4);
- Throwable exc = new NoUniqueBeanDefinitionException(ScheduledExecutorService.class);
- when(context.getBean(ScheduledExecutorService.class)).thenThrow(exc);
- when(context.getBean("taskScheduler", ScheduledExecutorService.class))
- .thenReturn(expected);
-
- configuration.configureTasks(registrar);
- TaskScheduler scheduler = registrar.getScheduler();
- assertTrue(scheduler instanceof ConcurrentTaskScheduler);
- assertEquals(expected, accessField(scheduler, "scheduledExecutor"));
- }
-
- @Test
- public void configureTasks_withProxyWrappedRegistrar() throws NoSuchFieldException, IllegalAccessException {
- ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
- when(context.getBean(TaskScheduler.class)).thenReturn(scheduler);
- TaskScheduler proxyScheduler = createProxy(scheduler);
- registrar.setScheduler(proxyScheduler);
- Object errorHandler = accessField(scheduler, "errorHandler");
- assertFalse(
- errorHandler instanceof BugsnagScheduledTaskExceptionHandler,
- "errorHandler should not be BugsnagScheduledTaskExceptionHandler"
- );
- configuration.configureTasks(registrar);
- errorHandler = accessField(scheduler, "errorHandler");
- assertTrue(
- "errorHandler should be BugsnagScheduledTaskExceptionHandler",
- errorHandler instanceof BugsnagScheduledTaskExceptionHandler
- );
- }
-
- private TaskScheduler createProxy(TaskScheduler target) {
- ProxyFactory factory = new ProxyFactory(target);
- factory.addAdvice((MethodInterceptor) invocation -> invocation.proceed());
- return (TaskScheduler) factory.getProxy();
- }
-
- private Object accessField(Object object, String fieldName)
- throws NoSuchFieldException, IllegalAccessException {
- Field field = object.getClass().getDeclaredField(fieldName);
- field.setAccessible(true);
- return field.get(object);
- }
-}
diff --git a/bugsnag-spring/src/javaxTest/java/com/bugsnag/SpringAsyncTest.java b/bugsnag-spring/src/javaxTest/java/com/bugsnag/SpringAsyncTest.java
deleted file mode 100644
index 1bdfa04b..00000000
--- a/bugsnag-spring/src/javaxTest/java/com/bugsnag/SpringAsyncTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package com.bugsnag;
-
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-
-import com.bugsnag.HandledState.SeverityReasonType;
-import com.bugsnag.delivery.Delivery;
-import com.bugsnag.testapp.springboot.AsyncService;
-import com.bugsnag.testapp.springboot.TestSpringBootApplication;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
-
-import java.util.Collections;
-
-/**
- * Test that a Spring Boot application configured with the
- * {@link BugsnagSpringConfiguration} performs as expected.
- */
-@RunWith(SpringRunner.class)
-@SpringBootTest(classes = TestSpringBootApplication.class)
-public class SpringAsyncTest {
-
- @Autowired
- private Bugsnag bugsnag;
-
- @Autowired
- private AsyncService asyncService;
-
- private Delivery delivery;
-
- /**
- * Initialize test state
- */
- @Before
- public void setUp() {
- delivery = mock(Delivery.class);
- bugsnag.setDelivery(delivery);
- }
-
- @Test
- public void bugsnagNotifyWhenAsyncVoidReturnTypeException() {
- asyncService.throwExceptionVoid();
-
- Report report = TestUtils.verifyAndGetReport(delivery);
-
- // Assert that the exception was detected correctly
- assertEquals("Async void test", report.getExceptionMessage());
- assertEquals("java.lang.RuntimeException", report.getExceptionName());
-
- // Assert that the severity, severity reason and unhandled values are correct
- Assert.assertEquals(Severity.ERROR.getValue(), report.getSeverity());
- assertEquals(
- SeverityReasonType.REASON_UNHANDLED_EXCEPTION_MIDDLEWARE.toString(),
- report.getSeverityReason().getType());
- assertThat(
- report.getSeverityReason().getAttributes(),
- is(Collections.singletonMap("framework", "Spring")));
- assertTrue(report.getUnhandled());
- }
-
- @Test
- public void bugsnagNotifyWhenAsyncFutureReturnTypeException() {
- asyncService.throwExceptionFuture();
-
- Report report = TestUtils.verifyAndGetReport(delivery);
-
- // Assert that the exception was detected correctly
- assertEquals("Async future test", report.getExceptionMessage());
- assertEquals("java.lang.RuntimeException", report.getExceptionName());
-
- // Assert that the severity, severity reason and unhandled values are correct
- assertEquals(Severity.ERROR.getValue(), report.getSeverity());
- assertEquals(
- SeverityReasonType.REASON_UNHANDLED_EXCEPTION_MIDDLEWARE.toString(),
- report.getSeverityReason().getType());
- assertThat(
- report.getSeverityReason().getAttributes(),
- is(Collections.singletonMap("framework", "Spring")));
- assertTrue(report.getUnhandled());
- }
-}
diff --git a/bugsnag-spring/src/javaxTest/java/com/bugsnag/SpringMvcTest.java b/bugsnag-spring/src/javaxTest/java/com/bugsnag/SpringMvcTest.java
deleted file mode 100644
index d426ba01..00000000
--- a/bugsnag-spring/src/javaxTest/java/com/bugsnag/SpringMvcTest.java
+++ /dev/null
@@ -1,282 +0,0 @@
-package com.bugsnag;
-
-import static com.bugsnag.TestUtils.anyMapOf;
-import static com.bugsnag.TestUtils.verifyAndGetReport;
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import com.bugsnag.HandledState.SeverityReasonType;
-import com.bugsnag.callbacks.Callback;
-import com.bugsnag.delivery.Delivery;
-import com.bugsnag.serialization.Serializer;
-import com.bugsnag.testapp.springboot.TestSpringBootApplication;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.SpringBootVersion;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
-import org.springframework.boot.test.web.client.TestRestTemplate;
-import org.springframework.boot.web.server.LocalServerPort;
-import org.springframework.core.SpringVersion;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpMethod;
-import org.springframework.test.context.junit4.SpringRunner;
-
-import java.lang.reflect.Field;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * Test that a Spring Boot application configured with the
- * {@link BugsnagSpringConfiguration} performs as expected.
- */
-@RunWith(SpringRunner.class)
-@SpringBootTest(
- classes = TestSpringBootApplication.class,
- webEnvironment = WebEnvironment.RANDOM_PORT)
-public class SpringMvcTest {
-
- @LocalServerPort
- private int randomServerPort;
-
- @Autowired
- private TestRestTemplate restTemplate;
-
- @Autowired
- private Bugsnag bugsnag;
-
- private Delivery delivery;
-
- private long sessionsStartedBeforeTest;
-
- /**
- * Initialize test state
- */
- @Before
- public void setUp() {
- delivery = mock(Delivery.class);
-
- bugsnag.setDelivery(delivery);
- bugsnag.getConfig().setSendUncaughtExceptions(true);
- bugsnag.getConfig().setAutoCaptureSessions(true);
-
- // Cannot reset the session count on the bugsnag bean for each test, so note
- // the current session count before the test starts instead.
- sessionsStartedBeforeTest = getSessionCount();
- }
-
- @Test
- public void bugsnagNotifyWhenUncaughtControllerException() {
- callRuntimeExceptionEndpoint();
-
- Report report = verifyAndGetReport(delivery);
-
- // Assert that the exception was detected correctly
- assertEquals("Test", report.getExceptionMessage());
- assertEquals("java.lang.RuntimeException", report.getExceptionName());
-
- // Assert that the severity, severity reason and unhandled values are correct
- Assert.assertEquals(Severity.ERROR.getValue(), report.getSeverity());
- assertEquals(
- SeverityReasonType.REASON_UNHANDLED_EXCEPTION_MIDDLEWARE.toString(),
- report.getSeverityReason().getType());
- assertThat(
- report.getSeverityReason().getAttributes(),
- is(Collections.singletonMap("framework", "Spring")));
- assertTrue(report.getUnhandled());
- }
-
- @Test
- public void noBugsnagNotifyWhenSendUncaughtExceptionsFalse() {
- bugsnag.getConfig().setSendUncaughtExceptions(false);
-
- callRuntimeExceptionEndpoint();
-
- verifyNoReport();
- }
-
- @Test
- public void bugsnagSessionStartedWhenAutoCaptureSessions() {
- callRuntimeExceptionEndpoint();
-
- assertSessionsStarted(1);
- }
-
- @Test
- public void noBugsnagSessionStartedWhenAutoCaptureSessionsFalse() {
- bugsnag.getConfig().setAutoCaptureSessions(false);
-
- callRuntimeExceptionEndpoint();
-
- assertSessionsStarted(0);
- }
-
- @Test
- public void requestMetadataSetCorrectly() {
- callRuntimeExceptionEndpoint();
-
- Report report = verifyAndGetReport(delivery);
-
- // Check that the context is set to the HTTP method and URI of the endpoint
- assertEquals("GET /throw-runtime-exception", report.getContext());
-
- // Check that the request metadata is set as expected
- @SuppressWarnings(value = "unchecked") Map requestMetadata =
- (Map) report.getMetaData().get("request");
- assertEquals("http://localhost:" + randomServerPort + "/throw-runtime-exception",
- requestMetadata.get("url"));
- assertEquals("GET", requestMetadata.get("method"));
- assertEquals("127.0.0.1", requestMetadata.get("clientIp"));
-
- // Assert that the request params are as expected
- @SuppressWarnings(value = "unchecked") Map params =
- (Map) requestMetadata.get("params");
- assertEquals("paramVal1", params.get("param1")[0]);
- assertEquals("paramVal2", params.get("param2")[0]);
-
- // Assert that the request headers are as expected, including headers with
- // multiple values represented as a comma-separated string.
- @SuppressWarnings(value = "unchecked") Map headers =
- (Map) requestMetadata.get("headers");
- assertEquals("header1Val1,header1Val2", headers.get("header1"));
- assertEquals("header2Val1", headers.get("header2"));
- }
-
- @Test
- @SuppressWarnings("unchecked")
- public void springVersionSetCorrectly() {
- callRuntimeExceptionEndpoint();
-
- Report report = verifyAndGetReport(delivery);
-
- // Check that the Spring version is set as expected
- Map deviceMetadata = report.getDevice();
- Map runtimeVersions =
- (Map) deviceMetadata.get("runtimeVersions");
- assertEquals(SpringVersion.getVersion(), runtimeVersions.get("springFramework"));
- assertEquals(SpringBootVersion.getVersion(), runtimeVersions.get("springBoot"));
- }
-
- @Test
- public void unhandledTypeMismatchExceptionSeverityInfo() {
- callUnhandledTypeMismatchExceptionEndpoint();
-
- Report report = verifyAndGetReport(delivery);
-
- assertTrue(report.getUnhandled());
- assertEquals("info", report.getSeverity());
- assertEquals("exceptionClass", report.getSeverityReason().getType());
- assertThat(report.getSeverityReason().getAttributes(),
- is(Collections.singletonMap("exceptionClass", "TypeMismatchException")));
- }
-
- @Test
- public void unhandledTypeMismatchExceptionCallbackSeverity()
- throws IllegalAccessException, NoSuchFieldException {
- Report report;
- Callback callback = new Callback() {
- @Override
- public void beforeNotify(Report report) {
- report.setSeverity(Severity.WARNING);
- }
- };
-
- try {
- bugsnag.addCallback(callback);
-
- callUnhandledTypeMismatchExceptionEndpoint();
-
- report = verifyAndGetReport(delivery);
- } finally {
- // Remove the callback via reflection so that subsequent tests do not use it
- Field callbacksField = Configuration.class.getDeclaredField("callbacks");
- @SuppressWarnings(value = "unchecked") Collection callbacks =
- (Collection) callbacksField.get(bugsnag.getConfig());
- callbacks.remove(callback);
- }
-
- assertTrue(report.getUnhandled());
- assertEquals("warning", report.getSeverity());
- assertEquals("userCallbackSetSeverity", report.getSeverityReason().getType());
- }
-
- @Test
- public void handledTypeMismatchExceptionUserSeverity() {
- callHandledTypeMismatchExceptionUserSeverityEndpoint();
-
- Report report = verifyAndGetReport(delivery);
-
- assertFalse(report.getUnhandled());
- assertEquals("warning", report.getSeverity());
- assertEquals("userSpecifiedSeverity", report.getSeverityReason().getType());
- assertThat(report.getSeverityReason().getAttributes(), is(Collections.EMPTY_MAP));
- }
-
- @Test
- public void handledTypeMismatchExceptionCallbackSeverity() {
- callHandledTypeMismatchExceptionCallbackSeverityEndpoint();
-
- Report report = verifyAndGetReport(delivery);
-
- assertFalse(report.getUnhandled());
- assertEquals("warning", report.getSeverity());
- assertEquals("userCallbackSetSeverity", report.getSeverityReason().getType());
- }
-
- private void callUnhandledTypeMismatchExceptionEndpoint() {
- this.restTemplate.getForEntity(
- "/throw-type-mismatch-exception", String.class);
- }
-
- private void callHandledTypeMismatchExceptionUserSeverityEndpoint() {
- this.restTemplate.getForEntity(
- "/handled-type-mismatch-exception-user-severity", String.class);
- }
-
- private void callHandledTypeMismatchExceptionCallbackSeverityEndpoint() {
- this.restTemplate.getForEntity(
- "/handled-type-mismatch-exception-callback-severity", String.class);
- }
-
- private void callRuntimeExceptionEndpoint() {
- HttpHeaders headers = new HttpHeaders();
- headers.add("header1", "header1Val1");
- headers.add("header1", "header1Val2");
- headers.add("header2", "header2Val1");
- HttpEntity entity = new HttpEntity("parameters", headers);
- this.restTemplate.exchange(
- "/throw-runtime-exception?param1=paramVal1¶m2=paramVal2",
- HttpMethod.GET,
- entity,
- String.class);
- }
-
- private void verifyNoReport() {
- verify(delivery, times(0)).deliver(
- any(Serializer.class),
- any(),
- anyMapOf(String.class, String.class));
- }
-
- private void assertSessionsStarted(int sessionsStarted) {
- assertEquals(sessionsStartedBeforeTest + sessionsStarted, getSessionCount());
- }
-
- private long getSessionCount() {
- return bugsnag.getSessionTracker().getBatchCount() != null
- ? bugsnag.getSessionTracker().getBatchCount().getSessionsStarted() : 0;
- }
-}
diff --git a/bugsnag-spring/src/javaxTest/java/com/bugsnag/SpringScheduledTaskTest.java b/bugsnag-spring/src/javaxTest/java/com/bugsnag/SpringScheduledTaskTest.java
deleted file mode 100644
index 7a86ff61..00000000
--- a/bugsnag-spring/src/javaxTest/java/com/bugsnag/SpringScheduledTaskTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package com.bugsnag;
-
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import com.bugsnag.HandledState.SeverityReasonType;
-import com.bugsnag.delivery.Delivery;
-import com.bugsnag.testapp.springboot.TestSpringBootApplication;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
-import org.springframework.test.context.junit4.SpringRunner;
-import org.springframework.util.ErrorHandler;
-
-import java.util.Collections;
-import java.util.concurrent.ExecutionException;
-
-/**
- * Test that a Spring Boot application configured with the
- * {@link BugsnagSpringConfiguration} performs as expected.
- */
-@RunWith(SpringRunner.class)
-@SpringBootTest(classes = TestSpringBootApplication.class)
-public class SpringScheduledTaskTest {
-
- @Autowired
- private Bugsnag bugsnag;
-
- @Autowired
- private ThreadPoolTaskScheduler scheduler;
-
- @MockBean
- private ErrorHandler mockErrorHandler;
-
- private Delivery delivery;
-
- /**
- * Initialize test state
- */
- @Before
- public void setUp() {
- delivery = mock(Delivery.class);
- bugsnag.setDelivery(delivery);
- }
-
- @Test
- public void bugsnagNotifyWhenScheduledTaskException()
- throws ExecutionException, InterruptedException {
-
- // The task to schedule
- Runnable exampleRunnable = new Runnable() {
- @Override
- public void run() {
- throw new RuntimeException("Scheduled test");
- }
- };
-
- // Run the task now and wait for it to finish
- scheduler.submit(exampleRunnable).get();
-
- Report report = TestUtils.verifyAndGetReport(delivery);
-
- // Assert that the exception was detected correctly
- assertEquals("Scheduled test", report.getExceptionMessage());
- assertEquals("java.lang.RuntimeException", report.getExceptionName());
-
- // Assert that the severity, severity reason and unhandled values are correct
- Assert.assertEquals(Severity.ERROR.getValue(), report.getSeverity());
- assertEquals(
- SeverityReasonType.REASON_UNHANDLED_EXCEPTION_MIDDLEWARE.toString(),
- report.getSeverityReason().getType());
- assertThat(
- report.getSeverityReason().getAttributes(),
- is(Collections.singletonMap("framework", "Spring")));
- assertTrue(report.getUnhandled());
-
- // Assert that the exception is passed to an existing exception handler
- ArgumentCaptor exceptionCaptor =
- ArgumentCaptor.forClass(RuntimeException.class);
- verify(mockErrorHandler, times(1)).handleError(exceptionCaptor.capture());
- assertEquals("Scheduled test", exceptionCaptor.getValue().getMessage());
- }
-}
diff --git a/bugsnag-spring/src/javaxTest/java/com/bugsnag/testapp/springboot/AsyncService.java b/bugsnag-spring/src/javaxTest/java/com/bugsnag/testapp/springboot/AsyncService.java
deleted file mode 100644
index 54376bac..00000000
--- a/bugsnag-spring/src/javaxTest/java/com/bugsnag/testapp/springboot/AsyncService.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.bugsnag.testapp.springboot;
-
-import org.springframework.scheduling.annotation.Async;
-import org.springframework.stereotype.Service;
-
-import java.util.concurrent.Future;
-
-@Service
-public class AsyncService {
- @Async
- public void throwExceptionVoid() {
- throw new RuntimeException("Async void test");
- }
-
- @Async
- public Future throwExceptionFuture() {
- throw new RuntimeException("Async future test");
- }
-}
diff --git a/bugsnag-spring/src/javaxTest/java/com/bugsnag/testapp/springboot/TestConfiguration.java b/bugsnag-spring/src/javaxTest/java/com/bugsnag/testapp/springboot/TestConfiguration.java
deleted file mode 100644
index c1691243..00000000
--- a/bugsnag-spring/src/javaxTest/java/com/bugsnag/testapp/springboot/TestConfiguration.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.bugsnag.testapp.springboot;
-
-import com.bugsnag.Bugsnag;
-import com.bugsnag.BugsnagAsyncExceptionHandler;
-import com.bugsnag.BugsnagSpringConfiguration;
-
-import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Import;
-import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
-import org.springframework.scheduling.annotation.SchedulingConfigurer;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
-import org.springframework.scheduling.config.ScheduledTaskRegistrar;
-import org.springframework.util.ErrorHandler;
-
-/**
- * This test configuration loads the BugsnagSpringConfiguration
- * that will be used for real Spring bugsnag integration.
- */
-@Configuration
-@Import(BugsnagSpringConfiguration.class)
-public class TestConfiguration extends AsyncConfigurerSupport implements SchedulingConfigurer {
-
- @Autowired(required = false)
- private ErrorHandler scheduledTaskErrorHandler;
-
- @Bean
- public Bugsnag bugsnag() {
- return new Bugsnag("apiKey");
- }
-
- @Bean
- ThreadPoolTaskScheduler scheduler() {
- ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
- taskScheduler.setErrorHandler(scheduledTaskErrorHandler);
- return taskScheduler;
- }
-
- @Override
- public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
- taskRegistrar.setScheduler(scheduler());
- }
-
- @Override
- public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
- return new BugsnagAsyncExceptionHandler(bugsnag());
- }
-}
diff --git a/bugsnag-spring/src/javaxTest/java/com/bugsnag/testapp/springboot/TestController.java b/bugsnag-spring/src/javaxTest/java/com/bugsnag/testapp/springboot/TestController.java
deleted file mode 100644
index 9d0091b5..00000000
--- a/bugsnag-spring/src/javaxTest/java/com/bugsnag/testapp/springboot/TestController.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package com.bugsnag.testapp.springboot;
-
-import com.bugsnag.Bugsnag;
-import com.bugsnag.Report;
-import com.bugsnag.Severity;
-import com.bugsnag.callbacks.Callback;
-
-import org.springframework.beans.TypeMismatchException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-public class TestController {
-
- @Autowired
- private Bugsnag bugsnag;
-
- /**
- * Throw a runtime exception
- */
- @RequestMapping("/throw-runtime-exception")
- public void throwRuntimeException() {
- throw new RuntimeException("Test");
- }
-
- /**
- * Throw an exception where the severity reason is exceptionClass
- */
- @RequestMapping("/throw-type-mismatch-exception")
- public void throwTypeMismatchException() {
- throw new TypeMismatchException("Test", String.class);
- }
-
- /**
- * Report a handled exception where the severity reason is exceptionClass
- */
- @RequestMapping("/handled-type-mismatch-exception")
- public void handledTypeMismatchException() {
- try {
- throw new TypeMismatchException("Test", String.class);
- } catch (TypeMismatchException ex) {
- bugsnag.notify(ex);
- }
- }
-
- /**
- * Report a handled exception where the severity is set in the notify call
- */
- @RequestMapping("/handled-type-mismatch-exception-user-severity")
- public void handledTypeMismatchExceptionUserSeverity() {
- try {
- throw new TypeMismatchException("Test", String.class);
- } catch (TypeMismatchException ex) {
- bugsnag.notify(ex, Severity.WARNING);
- }
- }
-
- /**
- * Report a handled exception where the severity reason is set in a callback
- */
- @RequestMapping("/handled-type-mismatch-exception-callback-severity")
- public void handledTypeMismatchExceptionCallbackSeverity() {
- try {
- throw new TypeMismatchException("Test", String.class);
- } catch (TypeMismatchException ex) {
- bugsnag.notify(ex, new Callback() {
- @Override
- public void beforeNotify(Report report) {
- report.setSeverity(Severity.WARNING);
- }
- });
- }
- }
-}
diff --git a/bugsnag-spring/src/javaxTest/java/com/bugsnag/testapp/springboot/TestSpringBootApplication.java b/bugsnag-spring/src/javaxTest/java/com/bugsnag/testapp/springboot/TestSpringBootApplication.java
deleted file mode 100644
index f02cde84..00000000
--- a/bugsnag-spring/src/javaxTest/java/com/bugsnag/testapp/springboot/TestSpringBootApplication.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.bugsnag.testapp.springboot;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.scheduling.annotation.EnableAsync;
-import org.springframework.scheduling.annotation.EnableScheduling;
-
-@SpringBootApplication
-@EnableScheduling
-@EnableAsync
-public class TestSpringBootApplication {
- public static void main(String[] args) {
- SpringApplication.run(TestSpringBootApplication.class, args);
- }
-}
diff --git a/bugsnag-spring/src/common/java/com/bugsnag/BugsnagAsyncExceptionHandler.java b/bugsnag-spring/src/main/java/com/bugsnag/BugsnagAsyncExceptionHandler.java
similarity index 100%
rename from bugsnag-spring/src/common/java/com/bugsnag/BugsnagAsyncExceptionHandler.java
rename to bugsnag-spring/src/main/java/com/bugsnag/BugsnagAsyncExceptionHandler.java
diff --git a/bugsnag-spring/src/jakarta/java/com/bugsnag/BugsnagJakartaMvcExceptionHandler.java b/bugsnag-spring/src/main/java/com/bugsnag/BugsnagJakartaMvcExceptionHandler.java
similarity index 100%
rename from bugsnag-spring/src/jakarta/java/com/bugsnag/BugsnagJakartaMvcExceptionHandler.java
rename to bugsnag-spring/src/main/java/com/bugsnag/BugsnagJakartaMvcExceptionHandler.java
diff --git a/bugsnag-spring/src/common/java/com/bugsnag/BugsnagScheduledTaskExceptionHandler.java b/bugsnag-spring/src/main/java/com/bugsnag/BugsnagScheduledTaskExceptionHandler.java
similarity index 100%
rename from bugsnag-spring/src/common/java/com/bugsnag/BugsnagScheduledTaskExceptionHandler.java
rename to bugsnag-spring/src/main/java/com/bugsnag/BugsnagScheduledTaskExceptionHandler.java
diff --git a/bugsnag-spring/src/common/java/com/bugsnag/BugsnagSpringConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java
similarity index 85%
rename from bugsnag-spring/src/common/java/com/bugsnag/BugsnagSpringConfiguration.java
rename to bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java
index 86d503ef..88aeae99 100644
--- a/bugsnag-spring/src/common/java/com/bugsnag/BugsnagSpringConfiguration.java
+++ b/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java
@@ -15,7 +15,7 @@
* Configuration to integrate Bugsnag with Spring.
*/
@Configuration
-@Import(BugsnagImportSelector.class)
+@Import({SpringBootJakartaConfiguration.class, JakartaMvcConfiguration.class, ScheduledTaskConfiguration.class})
public class BugsnagSpringConfiguration implements InitializingBean {
@Autowired
@@ -28,8 +28,9 @@ public class BugsnagSpringConfiguration implements InitializingBean {
Callback springVersionErrorCallback() {
Callback callback = new Callback() {
@Override
- public void beforeNotify(Report report) {
+ public boolean onError(Report report) {
addSpringRuntimeVersion(report.getDevice());
+ return true;
}
};
bugsnag.addCallback(callback);
@@ -37,15 +38,16 @@ public void beforeNotify(Report report) {
}
@Bean
- BeforeSendSession springVersionSessionCallback() {
- BeforeSendSession beforeSendSession = new BeforeSendSession() {
+ OnSession springVersionSessionCallback() {
+ OnSession onSession = new OnSession() {
@Override
- public void beforeSendSession(SessionPayload payload) {
+ public boolean onSession(SessionPayload payload) {
addSpringRuntimeVersion(payload.getDevice());
+ return true;
}
};
- bugsnag.addBeforeSendSession(beforeSendSession);
- return beforeSendSession;
+ bugsnag.addOnSession(onSession);
+ return onSession;
}
private void addSpringRuntimeVersion(Map device) {
diff --git a/bugsnag-spring/src/common/java/com/bugsnag/ExceptionClassCallback.java b/bugsnag-spring/src/main/java/com/bugsnag/ExceptionClassCallback.java
similarity index 98%
rename from bugsnag-spring/src/common/java/com/bugsnag/ExceptionClassCallback.java
rename to bugsnag-spring/src/main/java/com/bugsnag/ExceptionClassCallback.java
index 3c93b35b..008f09d1 100644
--- a/bugsnag-spring/src/common/java/com/bugsnag/ExceptionClassCallback.java
+++ b/bugsnag-spring/src/main/java/com/bugsnag/ExceptionClassCallback.java
@@ -111,7 +111,7 @@ class ExceptionClassCallback implements Callback {
}
@Override
- public void beforeNotify(Report report) {
+ public boolean onError(Report report) {
HandledState handledState = report.getHandledState();
@@ -119,7 +119,7 @@ public void beforeNotify(Report report) {
SeverityReasonType severityReasonType = handledState.calculateSeverityReasonType();
if (severityReasonType == SeverityReasonType.REASON_USER_SPECIFIED
|| severityReasonType == SeverityReasonType.REASON_CALLBACK_SPECIFIED) {
- return;
+ return true; // do not change delivery decision
}
Class exceptionClass = report.getException().getClass();
@@ -133,5 +133,6 @@ public void beforeNotify(Report report) {
severity,
handledState.isUnhandled()));
}
+ return true;
}
}
diff --git a/bugsnag-spring/src/jakarta/java/com/bugsnag/JakartaMvcConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/JakartaMvcConfiguration.java
similarity index 100%
rename from bugsnag-spring/src/jakarta/java/com/bugsnag/JakartaMvcConfiguration.java
rename to bugsnag-spring/src/main/java/com/bugsnag/JakartaMvcConfiguration.java
diff --git a/bugsnag-spring/src/common/java/com/bugsnag/ScheduledTaskBeanLocator.java b/bugsnag-spring/src/main/java/com/bugsnag/ScheduledTaskBeanLocator.java
similarity index 100%
rename from bugsnag-spring/src/common/java/com/bugsnag/ScheduledTaskBeanLocator.java
rename to bugsnag-spring/src/main/java/com/bugsnag/ScheduledTaskBeanLocator.java
diff --git a/bugsnag-spring/src/main/java/com/bugsnag/ScheduledTaskConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/ScheduledTaskConfiguration.java
new file mode 100644
index 00000000..9188d7f9
--- /dev/null
+++ b/bugsnag-spring/src/main/java/com/bugsnag/ScheduledTaskConfiguration.java
@@ -0,0 +1,197 @@
+package com.bugsnag;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.aop.framework.AopProxyUtils;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.annotation.SchedulingConfigurer;
+import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.scheduling.config.ScheduledTaskRegistrar;
+import org.springframework.util.ErrorHandler;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * Add configuration for reporting unhandled exceptions for scheduled tasks.
+ */
+@Configuration
+class ScheduledTaskConfiguration implements SchedulingConfigurer {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTaskConfiguration.class);
+
+ @Autowired
+ private Bugsnag bugsnag;
+
+ @Autowired
+ private ScheduledTaskBeanLocator beanLocator;
+
+ /**
+ * Optional: if the app defines a dedicated ErrorHandler bean for scheduled tasks
+ * (e.g. your @MockBean(name = "scheduledTaskErrorHandler") in tests), we can
+ * still chain it when we replace or wrap the scheduler.
+ */
+ @Autowired(required = false)
+ @Qualifier("scheduledTaskErrorHandler")
+ private ErrorHandler scheduledTaskErrorHandlerBean;
+
+ /**
+ * Add Bugsnag error handling to the task scheduler being used by Spring.
+ */
+ @Override
+ public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
+ BugsnagScheduledTaskExceptionHandler bugsnagErrorHandler =
+ new BugsnagScheduledTaskExceptionHandler(bugsnag);
+
+ // Decision process for finding a TaskScheduler, in order of preference:
+ // 1. use the scheduler from the task registrar
+ // 2. search for a TaskScheduler bean, by type, then by name
+ // 3. search for a ScheduledExecutorService bean by type, then by name, and wrap it
+ // 4. create our own TaskScheduler
+ TaskScheduler registrarScheduler = taskRegistrar.getScheduler();
+ TaskScheduler taskScheduler = registrarScheduler != null
+ ? registrarScheduler
+ : beanLocator.resolveTaskScheduler();
+
+ if (taskScheduler != null) {
+ // Spring Boot 3 creates a TaskSchedulerRouter which cannot be configured.
+ // In this case, create our own scheduler instead (but preserve any bean-level handler).
+ String schedulerClassName = taskScheduler.getClass().getName();
+ if (schedulerClassName.equals("org.springframework.scheduling.config.TaskSchedulerRouter")) {
+ ScheduledExecutorService executorService = beanLocator.resolveScheduledExecutorService();
+ chainExistingBeanHandlerIfPresent(bugsnagErrorHandler);
+ taskScheduler = createNewTaskScheduler(executorService, bugsnagErrorHandler);
+ taskRegistrar.setScheduler(taskScheduler);
+ return;
+ }
+
+ // If it's a proxy, unwrap to the target to allow reflection / method calls.
+ if (AopUtils.isAopProxy(taskScheduler)) {
+ Class> targetClass = AopProxyUtils.ultimateTargetClass(taskScheduler);
+ if (TaskScheduler.class.isAssignableFrom(targetClass)) {
+ TaskScheduler target = (TaskScheduler) AopProxyUtils.getSingletonTarget(taskScheduler);
+ if (target != null) {
+ taskScheduler = target;
+ }
+ }
+ }
+
+ configureExistingTaskScheduler(taskScheduler, bugsnagErrorHandler);
+ } else {
+ // No scheduler has been defined by the application, create one and add the Bugsnag error handler.
+ ScheduledExecutorService executorService = beanLocator.resolveScheduledExecutorService();
+ chainExistingBeanHandlerIfPresent(bugsnagErrorHandler);
+ taskScheduler = createNewTaskScheduler(executorService, bugsnagErrorHandler);
+ taskRegistrar.setScheduler(taskScheduler);
+ }
+ }
+
+ private TaskScheduler createNewTaskScheduler(
+ ScheduledExecutorService executorService,
+ BugsnagScheduledTaskExceptionHandler errorHandler
+ ) {
+ if (executorService != null) {
+ // Create a task scheduler which delegates to the existing Executor
+ ConcurrentTaskScheduler scheduler = new ConcurrentTaskScheduler(executorService);
+ scheduler.setErrorHandler(errorHandler);
+ return scheduler;
+ } else {
+ // If no task scheduler has been defined by the application, create one and add the Bugsnag error handler.
+ ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+ scheduler.setErrorHandler(errorHandler);
+ scheduler.initialize();
+ return scheduler;
+ }
+ }
+
+ /**
+ * If a task scheduler has been defined by the application, configure it so that Bugsnag error handling is added.
+ * We first capture any existing ErrorHandler (e.g. your mock bean), chain it into the Bugsnag handler,
+ * then set the Bugsnag handler via setter if available (Boot 3+) or via field reflection.
+ */
+ private void configureExistingTaskScheduler(
+ TaskScheduler taskScheduler,
+ BugsnagScheduledTaskExceptionHandler errorHandler
+ ) {
+ // (1) Capture whatever handler is already configured on the scheduler
+ ErrorHandler existing = extractExistingErrorHandler(taskScheduler);
+ if (existing != null) {
+ errorHandler.setExistingErrorHandler(existing);
+ } else if (scheduledTaskErrorHandlerBean != null) {
+ // Fallback: chain the bean-level handler if the scheduler didn't have one yet
+ errorHandler.setExistingErrorHandler(scheduledTaskErrorHandlerBean);
+ }
+
+ // (2) Install the Bugsnag handler via public setter if available, else via private field
+ if (trySetErrorHandlerViaMethod(taskScheduler, errorHandler)) {
+ return;
+ }
+ trySetErrorHandlerViaField(taskScheduler, errorHandler);
+ }
+
+ /**
+ * Chain the dedicated bean-level handler if present (useful when we replace the scheduler).
+ */
+ private void chainExistingBeanHandlerIfPresent(BugsnagScheduledTaskExceptionHandler errorHandler) {
+ if (scheduledTaskErrorHandlerBean != null) {
+ errorHandler.setExistingErrorHandler(scheduledTaskErrorHandlerBean);
+ }
+ }
+
+ /**
+ * Prefer a public getter if present; otherwise fall back to private field.
+ */
+ private ErrorHandler extractExistingErrorHandler(TaskScheduler taskScheduler) {
+ // Try public getter (present on ThreadPoolTaskScheduler et al.)
+ try {
+ Method getter = taskScheduler.getClass().getMethod("getErrorHandler");
+ Object val = getter.invoke(taskScheduler);
+ if (val instanceof ErrorHandler) {
+ return (ErrorHandler) val;
+ }
+ } catch (Throwable ignore) {
+ // no-op
+ }
+
+ // Fall back to private field access
+ try {
+ Field fld = taskScheduler.getClass().getDeclaredField("errorHandler");
+ fld.setAccessible(true);
+ Object val = fld.get(taskScheduler);
+ if (val instanceof ErrorHandler) {
+ return (ErrorHandler) val;
+ }
+ } catch (Throwable ignore) {
+ // no-op
+ }
+
+ return null;
+ }
+
+ private boolean trySetErrorHandlerViaMethod(TaskScheduler taskScheduler, ErrorHandler handler) {
+ try {
+ Method setter = taskScheduler.getClass().getMethod("setErrorHandler", ErrorHandler.class);
+ setter.invoke(taskScheduler, handler);
+ return true;
+ } catch (Throwable ex) {
+ return false;
+ }
+ }
+
+ private boolean trySetErrorHandlerViaField(TaskScheduler taskScheduler, ErrorHandler handler) {
+ try {
+ Field fld = taskScheduler.getClass().getDeclaredField("errorHandler");
+ fld.setAccessible(true);
+ fld.set(taskScheduler, handler);
+ return true;
+ } catch (Throwable ex) {
+ return false;
+ }
+ }
+}
diff --git a/bugsnag-spring/src/common/java/com/bugsnag/SpringBootConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java
similarity index 74%
rename from bugsnag-spring/src/common/java/com/bugsnag/SpringBootConfiguration.java
rename to bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java
index f8f71ab0..aa5509ec 100644
--- a/bugsnag-spring/src/common/java/com/bugsnag/SpringBootConfiguration.java
+++ b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java
@@ -19,8 +19,9 @@ public class SpringBootConfiguration {
Callback springBootVersionErrorCallback() {
Callback callback = new Callback() {
@Override
- public void beforeNotify(Report report) {
+ public boolean onError(Report report) {
addSpringRuntimeVersion(report.getDevice());
+ return true;
}
};
bugsnag.addCallback(callback);
@@ -28,15 +29,16 @@ public void beforeNotify(Report report) {
}
@Bean
- BeforeSendSession springBootVersionSessionCallback() {
- BeforeSendSession beforeSendSession = new BeforeSendSession() {
+ OnSession springBootVersionSessionCallback() {
+ OnSession onSession = new OnSession() {
@Override
- public void beforeSendSession(SessionPayload payload) {
+ public boolean onSession(SessionPayload payload) {
addSpringRuntimeVersion(payload.getDevice());
+ return true;
}
};
- bugsnag.addBeforeSendSession(beforeSendSession);
- return beforeSendSession;
+ bugsnag.addOnSession(onSession);
+ return onSession;
}
private void addSpringRuntimeVersion(Map device) {
diff --git a/bugsnag-spring/src/jakarta/java/com/bugsnag/SpringBootJakartaConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootJakartaConfiguration.java
similarity index 78%
rename from bugsnag-spring/src/jakarta/java/com/bugsnag/SpringBootJakartaConfiguration.java
rename to bugsnag-spring/src/main/java/com/bugsnag/SpringBootJakartaConfiguration.java
index c9f0d413..06e02edf 100644
--- a/bugsnag-spring/src/jakarta/java/com/bugsnag/SpringBootJakartaConfiguration.java
+++ b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootJakartaConfiguration.java
@@ -16,9 +16,8 @@
class SpringBootJakartaConfiguration extends SpringBootConfiguration {
/**
- * The {@link com.bugsnag.servlet.javax.BugsnagServletContainerInitializer} does not work for Spring Boot, need to
- * register the {@link BugsnagServletRequestListener} using a Spring Boot
- * {@link ServletListenerRegistrationBean} instead. This adds session tracking and
+ * Spring Boot requires manual registration of the {@link BugsnagServletRequestListener} using a Spring Boot
+ * {@link ServletListenerRegistrationBean}. This adds session tracking and
* automatic servlet request metadata collection.
*/
@Bean
diff --git a/bugsnag-spring/src/common/java/com/bugsnag/SpringBootLoadedCondition.java b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootLoadedCondition.java
similarity index 100%
rename from bugsnag-spring/src/common/java/com/bugsnag/SpringBootLoadedCondition.java
rename to bugsnag-spring/src/main/java/com/bugsnag/SpringBootLoadedCondition.java
diff --git a/bugsnag-spring/src/common/java/com/bugsnag/SpringWebMvcLoadedCondition.java b/bugsnag-spring/src/main/java/com/bugsnag/SpringWebMvcLoadedCondition.java
similarity index 100%
rename from bugsnag-spring/src/common/java/com/bugsnag/SpringWebMvcLoadedCondition.java
rename to bugsnag-spring/src/main/java/com/bugsnag/SpringWebMvcLoadedCondition.java
diff --git a/bugsnag-spring/src/jakartaTest/java/com/bugsnag/NotifierTest.java b/bugsnag-spring/src/test/java/com/bugsnag/NotifierTest.java
similarity index 100%
rename from bugsnag-spring/src/jakartaTest/java/com/bugsnag/NotifierTest.java
rename to bugsnag-spring/src/test/java/com/bugsnag/NotifierTest.java
diff --git a/bugsnag-spring/src/jakartaTest/java/com/bugsnag/ScheduledTaskBeanLocatorTest.java b/bugsnag-spring/src/test/java/com/bugsnag/ScheduledTaskBeanLocatorTest.java
similarity index 100%
rename from bugsnag-spring/src/jakartaTest/java/com/bugsnag/ScheduledTaskBeanLocatorTest.java
rename to bugsnag-spring/src/test/java/com/bugsnag/ScheduledTaskBeanLocatorTest.java
diff --git a/bugsnag-spring/src/jakartaTest/java/com/bugsnag/ScheduledTaskConfigurationTest.java b/bugsnag-spring/src/test/java/com/bugsnag/ScheduledTaskConfigurationTest.java
similarity index 100%
rename from bugsnag-spring/src/jakartaTest/java/com/bugsnag/ScheduledTaskConfigurationTest.java
rename to bugsnag-spring/src/test/java/com/bugsnag/ScheduledTaskConfigurationTest.java
diff --git a/bugsnag-spring/src/jakartaTest/java/com/bugsnag/SpringAsyncTest.java b/bugsnag-spring/src/test/java/com/bugsnag/SpringAsyncTest.java
similarity index 100%
rename from bugsnag-spring/src/jakartaTest/java/com/bugsnag/SpringAsyncTest.java
rename to bugsnag-spring/src/test/java/com/bugsnag/SpringAsyncTest.java
diff --git a/bugsnag-spring/src/jakartaTest/java/com/bugsnag/SpringMvcTest.java b/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java
similarity index 98%
rename from bugsnag-spring/src/jakartaTest/java/com/bugsnag/SpringMvcTest.java
rename to bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java
index ed03f19f..fcded466 100644
--- a/bugsnag-spring/src/jakartaTest/java/com/bugsnag/SpringMvcTest.java
+++ b/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java
@@ -136,7 +136,7 @@ public void requestMetadataSetCorrectly() {
// Check that the request metadata is set as expected
@SuppressWarnings(value = "unchecked") Map requestMetadata =
- (Map) report.getMetaData().get("request");
+ (Map) report.getMetadata().get("request");
assertEquals("http://localhost:" + randomServerPort + "/throw-runtime-exception",
requestMetadata.get("url"));
assertEquals("GET", requestMetadata.get("method"));
@@ -190,8 +190,9 @@ public void unhandledTypeMismatchExceptionCallbackSeverity()
Report report;
Callback callback = new Callback() {
@Override
- public void beforeNotify(Report report) {
+ public boolean onError(Report report) {
report.setSeverity(Severity.WARNING);
+ return true;
}
};
diff --git a/bugsnag-spring/src/jakartaTest/java/com/bugsnag/SpringScheduledTaskTest.java b/bugsnag-spring/src/test/java/com/bugsnag/SpringScheduledTaskTest.java
similarity index 100%
rename from bugsnag-spring/src/jakartaTest/java/com/bugsnag/SpringScheduledTaskTest.java
rename to bugsnag-spring/src/test/java/com/bugsnag/SpringScheduledTaskTest.java
diff --git a/bugsnag-spring/src/commonTest/java/com/bugsnag/TestUtils.java b/bugsnag-spring/src/test/java/com/bugsnag/TestUtils.java
similarity index 100%
rename from bugsnag-spring/src/commonTest/java/com/bugsnag/TestUtils.java
rename to bugsnag-spring/src/test/java/com/bugsnag/TestUtils.java
diff --git a/bugsnag-spring/src/jakartaTest/java/com/bugsnag/testapp/springboot/AsyncService.java b/bugsnag-spring/src/test/java/com/bugsnag/testapp/springboot/AsyncService.java
similarity index 100%
rename from bugsnag-spring/src/jakartaTest/java/com/bugsnag/testapp/springboot/AsyncService.java
rename to bugsnag-spring/src/test/java/com/bugsnag/testapp/springboot/AsyncService.java
diff --git a/bugsnag-spring/src/jakartaTest/java/com/bugsnag/testapp/springboot/TestConfiguration.java b/bugsnag-spring/src/test/java/com/bugsnag/testapp/springboot/TestConfiguration.java
similarity index 100%
rename from bugsnag-spring/src/jakartaTest/java/com/bugsnag/testapp/springboot/TestConfiguration.java
rename to bugsnag-spring/src/test/java/com/bugsnag/testapp/springboot/TestConfiguration.java
diff --git a/bugsnag-spring/src/jakartaTest/java/com/bugsnag/testapp/springboot/TestController.java b/bugsnag-spring/src/test/java/com/bugsnag/testapp/springboot/TestController.java
similarity index 96%
rename from bugsnag-spring/src/jakartaTest/java/com/bugsnag/testapp/springboot/TestController.java
rename to bugsnag-spring/src/test/java/com/bugsnag/testapp/springboot/TestController.java
index 86e064bf..650bbf98 100644
--- a/bugsnag-spring/src/jakartaTest/java/com/bugsnag/testapp/springboot/TestController.java
+++ b/bugsnag-spring/src/test/java/com/bugsnag/testapp/springboot/TestController.java
@@ -67,8 +67,9 @@ public void handledTypeMismatchExceptionCallbackSeverity() {
} catch (TypeMismatchException ex) {
bugsnag.notify(ex, new Callback() {
@Override
- public void beforeNotify(Report report) {
+ public boolean onError(Report report) {
report.setSeverity(Severity.WARNING);
+ return true;
}
});
}
diff --git a/bugsnag-spring/src/jakartaTest/java/com/bugsnag/testapp/springboot/TestSpringBootApplication.java b/bugsnag-spring/src/test/java/com/bugsnag/testapp/springboot/TestSpringBootApplication.java
similarity index 100%
rename from bugsnag-spring/src/jakartaTest/java/com/bugsnag/testapp/springboot/TestSpringBootApplication.java
rename to bugsnag-spring/src/test/java/com/bugsnag/testapp/springboot/TestSpringBootApplication.java
diff --git a/bugsnag/build.gradle b/bugsnag/build.gradle
deleted file mode 100644
index 140e8a75..00000000
--- a/bugsnag/build.gradle
+++ /dev/null
@@ -1,53 +0,0 @@
-plugins {
- id "com.github.hierynomus.license" version "0.16.1"
-}
-
-apply plugin: 'java-library'
-apply from: '../common.gradle'
-
-compileJava {
- sourceCompatibility = '1.7'
- targetCompatibility = '1.7'
-}
-
-compileTestJava {
- sourceCompatibility = '1.7'
- targetCompatibility = '1.7'
-}
-
-repositories {
- mavenCentral()
-}
-
-dependencies {
- api "com.fasterxml.jackson.core:jackson-databind:2.14.1"
- api "org.slf4j:slf4j-api:${slf4jApiVersion}"
- compileOnly "javax.servlet:javax.servlet-api:${javaxServletApiVersion}"
- compileOnly "jakarta.servlet:jakarta.servlet-api:${jakartaServletApiVersion}"
- compileOnly("ch.qos.logback:logback-classic:${logbackVersion}") {
- exclude group: "org.slf4j"
- }
-
- testImplementation "junit:junit:${junitVersion}"
- testImplementation "org.slf4j:log4j-over-slf4j:${slf4jApiVersion}"
- testImplementation "javax.servlet:javax.servlet-api:${javaxServletApiVersion}"
- testImplementation "jakarta.servlet:jakarta.servlet-api:${jakartaServletApiVersion}"
- testImplementation "org.mockito:mockito-core:${mockitoVersion}"
- testImplementation("ch.qos.logback:logback-classic:${logbackVersion}") {
- exclude group: "org.slf4j"
- }
-}
-
-// license checking
-license {
- header rootProject.file('LICENSE')
- ignoreFailures true
-}
-
-downloadLicenses {
- dependencyConfiguration "compile"
-}
-
-java {
- withJavadocJar()
-}
\ No newline at end of file
diff --git a/bugsnag/build.gradle.kts b/bugsnag/build.gradle.kts
new file mode 100644
index 00000000..8b0845c4
--- /dev/null
+++ b/bugsnag/build.gradle.kts
@@ -0,0 +1,57 @@
+plugins {
+ alias(libs.plugins.license)
+ `java-library`
+}
+
+apply(from = "../common.gradle.kts")
+
+java {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(17))
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ api(libs.jackson.databind)
+ api(libs.slf4j.api)
+ compileOnly(libs.jakarta.servlet.api)
+ compileOnly(libs.logback.classic) {
+ exclude(group = "org.slf4j")
+ }
+
+ testImplementation(libs.junit)
+ testImplementation(libs.log4j.over.slf4j)
+ testImplementation(libs.jakarta.servlet.api)
+ testImplementation(libs.mockito.core)
+ testImplementation(libs.logback.classic) {
+ exclude(group = "org.slf4j")
+ }
+}
+
+// license checking
+configure {
+ header = rootProject.file("LICENSE")
+ isIgnoreFailures = true
+}
+
+tasks.named("downloadLicenses") {
+ // Note: dependencyConfiguration property needs to be set through the plugin's DSL
+ // This may require checking the plugin documentation for Kotlin DSL syntax
+}
+
+java {
+ withJavadocJar()
+}
+
+/** ---- Publishing config ----
+ * Pulls in publishing+signing rules from the shared release.gradle.kts.
+ * This will create tasks like:
+ * :bugsnag:publishMavenJavaPublicationToTestRepository
+ * :bugsnag:publishMavenJavaPublicationToOssrhStagingRepository
+ */
+apply(from = "${rootProject.projectDir}/release.gradle.kts")
+
diff --git a/bugsnag/src/main/java/com/bugsnag/BeforeSendSession.java b/bugsnag/src/main/java/com/bugsnag/BeforeSendSession.java
deleted file mode 100644
index dcd6a50d..00000000
--- a/bugsnag/src/main/java/com/bugsnag/BeforeSendSession.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.bugsnag;
-
-interface BeforeSendSession {
- void beforeSendSession(SessionPayload payload);
-}
diff --git a/bugsnag/src/main/java/com/bugsnag/Bugsnag.java b/bugsnag/src/main/java/com/bugsnag/Bugsnag.java
index abfaffd0..8914ac15 100644
--- a/bugsnag/src/main/java/com/bugsnag/Bugsnag.java
+++ b/bugsnag/src/main/java/com/bugsnag/Bugsnag.java
@@ -58,10 +58,10 @@ public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) {
private Configuration config;
private final SessionTracker sessionTracker;
- private static final ThreadLocal THREAD_METADATA = new ThreadLocal() {
+ private static final ThreadLocal THREAD_METADATA = new ThreadLocal() {
@Override
- public MetaData initialValue() {
- return new MetaData();
+ public Metadata initialValue() {
+ return new Metadata();
}
};
@@ -151,7 +151,7 @@ public void addCallback(Callback callback) {
* @see Delivery
*/
public Delivery getDelivery() {
- return config.delivery;
+ return config.getDelivery();
}
/**
@@ -161,17 +161,16 @@ public Delivery getDelivery() {
* @see Delivery
*/
public Delivery getSessionDelivery() {
- return config.sessionDelivery;
+ return config.getSessionDelivery();
}
-
/**
* Set the application type sent to Bugsnag.
*
* @param appType the app type to send, eg. spring, gradleTask
*/
public void setAppType(String appType) {
- config.appType = appType;
+ config.setAppType(appType);
}
/**
@@ -180,7 +179,7 @@ public void setAppType(String appType) {
* @param appVersion the app version to send
*/
public void setAppVersion(String appVersion) {
- config.appVersion = appVersion;
+ config.setAppVersion(appVersion);
}
/**
@@ -194,10 +193,9 @@ public void setAppVersion(String appVersion) {
* @see Delivery
*/
public void setDelivery(Delivery delivery) {
- config.delivery = delivery;
+ config.setDelivery(delivery);
}
-
/**
* Set the method of delivery for Bugsnag sessions. By default we'll
* send sessions asynchronously using a thread pool to
@@ -209,7 +207,7 @@ public void setDelivery(Delivery delivery) {
* @see Delivery
*/
public void setSessionDelivery(Delivery delivery) {
- config.sessionDelivery = delivery;
+ config.setSessionDelivery(delivery);
}
/**
@@ -222,42 +220,47 @@ public void setSessionDelivery(Delivery delivery) {
*/
@Deprecated
public void setEndpoint(String endpoint) {
- if (config.delivery instanceof HttpDelivery) {
- ((HttpDelivery) config.delivery).setEndpoint(endpoint);
+ Delivery delivery = config.getDelivery();
+ if (delivery instanceof HttpDelivery) {
+ ((HttpDelivery) delivery).setEndpoint(endpoint);
}
}
/**
- * Set which keys should be filtered when sending metaData to Bugsnag.
+ * Set which keys should be redacted when sending metadata to Bugsnag.
* Use this when you want to ensure sensitive information, such as passwords
- * or credit card information is stripped from metaData you send to Bugsnag.
- * Any keys in metaData which contain these strings will be marked as
- * [FILTERED] when send to Bugsnag.
+ * or credit card information is stripped from metadata you send to Bugsnag.
+ * Any keys in metadata which contain these strings will be marked as
+ * [REDACTED] when send to Bugsnag.
*
- * @param filters a list of String keys to filter from metaData
+ * @param redactedKeys a list of String keys to redact from metadata
*/
- public void setFilters(String... filters) {
- config.filters = filters;
+ public void setRedactedKeys(String... redactedKeys) {
+ config.setRedactedKeys(redactedKeys);
}
/**
* Set which exception classes should be ignored (not sent) by Bugsnag.
*
- * @param ignoreClasses a list of exception classes to ignore
+ * @param discardClasses a list of exception classes to ignore
*/
- public void setIgnoreClasses(String... ignoreClasses) {
- config.ignoreClasses = ignoreClasses;
+ public void setDiscardClasses(String... discardClasses) {
+ config.setDiscardClasses(discardClasses);
}
/**
* Set for which releaseStages errors should be sent to Bugsnag.
* Use this to stop errors from development builds being sent.
*
- * @param notifyReleaseStages a list of releaseStages to notify for
+ * @param enabledReleaseStages a list of releaseStages to notify for
* @see #setReleaseStage
*/
- public void setNotifyReleaseStages(String... notifyReleaseStages) {
- config.notifyReleaseStages = notifyReleaseStages;
+ public void setEnabledReleaseStages(String... enabledReleaseStages) {
+ if (enabledReleaseStages == null || enabledReleaseStages.length == 0) {
+ config.setEnabledReleaseStages(Collections.emptySet());
+ } else {
+ config.setEnabledReleaseStages(Set.of(enabledReleaseStages));
+ }
}
/**
@@ -267,21 +270,25 @@ public void setNotifyReleaseStages(String... notifyReleaseStages) {
* @param projectPackages a list of package names
*/
public void setProjectPackages(String... projectPackages) {
- config.projectPackages = projectPackages;
+ config.setProjectPackages(projectPackages);
}
/**
- * Set a proxy to use when delivering Bugsnag error reports and sessions. This is a convenient
+ * Set a proxy to use when delivering Bugsnag error reports and sessions. This
+ * is a convenient
* shorthand for bugsnag.getDelivery().setProxy();
*
* @param proxy the proxy to use to send reports
*/
public void setProxy(Proxy proxy) {
- if (config.delivery instanceof HttpDelivery) {
- ((HttpDelivery) config.delivery).setProxy(proxy);
+ Delivery delivery = config.getDelivery();
+ if (delivery instanceof HttpDelivery) {
+ ((HttpDelivery) delivery).setProxy(proxy);
}
- if (config.sessionDelivery instanceof HttpDelivery) {
- ((HttpDelivery) config.sessionDelivery).setProxy(proxy);
+
+ Delivery sessionDelivery = config.getSessionDelivery();
+ if (sessionDelivery instanceof HttpDelivery) {
+ ((HttpDelivery) sessionDelivery).setProxy(proxy);
}
}
@@ -289,10 +296,10 @@ public void setProxy(Proxy proxy) {
* Set the current "release stage" of your application.
*
* @param releaseStage the release stage of the app
- * @see #setNotifyReleaseStages
+ * @see #setEnabledReleaseStages
*/
public void setReleaseStage(String releaseStage) {
- config.releaseStage = releaseStage;
+ config.setReleaseStage(releaseStage);
}
/**
@@ -302,29 +309,32 @@ public void setReleaseStage(String releaseStage) {
* environment.
*
* @param sendThreads should we send thread state with error reports
- * @see #setNotifyReleaseStages
+ * @see #setEnabledReleaseStages
*/
public void setSendThreads(boolean sendThreads) {
- config.sendThreads = sendThreads;
+ config.setSendThreads(sendThreads);
}
/**
- * Set a timeout (in ms) to use when delivering Bugsnag error reports and sessions.
+ * Set a timeout (in ms) to use when delivering Bugsnag error reports and
+ * sessions.
* This is a convenient shorthand for bugsnag.getDelivery().setTimeout();
*
* @param timeout the timeout to set (in ms)
* @see #setDelivery
*/
public void setTimeout(int timeout) {
- if (config.delivery instanceof HttpDelivery) {
- ((HttpDelivery) config.delivery).setTimeout(timeout);
+ Delivery delivery = config.getDelivery();
+ if (delivery instanceof HttpDelivery) {
+ ((HttpDelivery) delivery).setTimeout(timeout);
}
- if (config.sessionDelivery instanceof HttpDelivery) {
- ((HttpDelivery) config.sessionDelivery).setTimeout(timeout);
+
+ Delivery sessionDelivery = config.getSessionDelivery();
+ if (sessionDelivery instanceof HttpDelivery) {
+ ((HttpDelivery) sessionDelivery).setTimeout(timeout);
}
}
-
//
// Notification
//
@@ -411,7 +421,6 @@ public boolean notify(Report report) {
return notify(report, null);
}
-
boolean notify(Throwable throwable, HandledState handledState, Thread currentThread) {
Report report = new Report(config, throwable, handledState, currentThread);
return notify(report, null);
@@ -435,28 +444,24 @@ public boolean notify(Report report, Callback reportCallback) {
// Don't notify if this error class should be ignored
if (config.shouldIgnoreClass(report.getExceptionName())) {
- LOGGER.debug("Error not reported to Bugsnag - {} is in 'ignoreClasses'",
+ LOGGER.debug("Error not reported to Bugsnag - {} is in 'discardClasses'",
report.getExceptionName());
return false;
}
- // Don't notify unless releaseStage is in notifyReleaseStages
+ // Don't notify unless releaseStage is in enabledReleaseStages
if (!config.shouldNotifyForReleaseStage()) {
- LOGGER.debug("Error not reported to Bugsnag - {} is not in 'notifyReleaseStages'",
- config.releaseStage);
+ LOGGER.debug("Error not reported to Bugsnag - {} is not in 'enabledReleaseStages'",
+ config.getReleaseStage());
return false;
}
- // Run all client-wide beforeNotify callbacks
+ // Run all client-wide onError callbacks
for (Callback callback : config.callbacks) {
try {
- // Run the callback
- callback.beforeNotify(report);
-
- // Check if callback cancelled delivery
- if (report.getShouldCancel()) {
- LOGGER.debug("Error not reported to Bugsnag - "
- + "cancelled by a client-wide beforeNotify callback");
+ boolean proceed = callback.onError(report);
+ if (!proceed || report.getShouldCancel()) {
+ LOGGER.debug("Error not reported to Bugsnag - cancelled by a client-wide onError callback");
return false;
}
} catch (Throwable ex) {
@@ -465,18 +470,14 @@ public boolean notify(Report report, Callback reportCallback) {
}
// Add thread metadata to the report
- report.mergeMetaData(THREAD_METADATA.get());
+ report.mergeMetadata(THREAD_METADATA.get());
- // Run the report-specific beforeNotify callback, if given
+ // Run the report-specific onError callback, if given
if (reportCallback != null) {
try {
- // Run the callback
- reportCallback.beforeNotify(report);
-
- // Check if callback cancelled delivery
- if (report.getShouldCancel()) {
- LOGGER.debug(
- "Error not reported to Bugsnag - cancelled by a report-specific callback");
+ boolean proceed = reportCallback.onError(report);
+ if (!proceed || report.getShouldCancel()) {
+ LOGGER.debug("Error not reported to Bugsnag - cancelled by a report-specific callback");
return false;
}
} catch (Throwable ex) {
@@ -484,7 +485,8 @@ public boolean notify(Report report, Callback reportCallback) {
}
}
- if (config.delivery == null) {
+ Delivery delivery = config.getDelivery();
+ if (delivery == null) {
LOGGER.debug("Error not reported to Bugsnag - no delivery is set");
return false;
}
@@ -507,7 +509,7 @@ public boolean notify(Report report, Callback reportCallback) {
// Deliver the notification
LOGGER.debug("Reporting error to Bugsnag");
- config.delivery.deliver(config.serializer, notification, config.getErrorApiHeaders());
+ delivery.deliver(config.getSerializer(), notification, config.getErrorApiHeaders());
return true;
}
@@ -559,8 +561,9 @@ public boolean shouldAutoCaptureSessions() {
*/
@Deprecated
public void setSessionEndpoint(String endpoint) {
- if (config.sessionDelivery instanceof HttpDelivery) {
- ((HttpDelivery) config.sessionDelivery).setEndpoint(endpoint);
+ Delivery sessionDelivery = config.getSessionDelivery();
+ if (sessionDelivery instanceof HttpDelivery) {
+ ((HttpDelivery) sessionDelivery).setEndpoint(endpoint);
}
}
@@ -598,14 +601,16 @@ public void close() {
LOGGER.debug("Closing connection to Bugsnag");
ExceptionHandler.disable(this);
- // runs periodic checks, should shut down immediately as don't need to send any sessions
+ // runs periodic checks, should shut down immediately as don't need to send any
+ // sessions
sessionExecutorService.shutdownNow();
// flush remaining sessions
sessionTracker.shutdown();
- if (config.delivery != null) {
- config.delivery.close();
+ Delivery delivery = config.getDelivery();
+ if (delivery != null) {
+ delivery.close();
}
}
@@ -618,14 +623,14 @@ public void close() {
* @param key the key of the metadata to add
* @param value the metadata value to add
*/
- public static void addThreadMetaData(String tabName, String key, Object value) {
- THREAD_METADATA.get().addToTab(tabName, key, value);
+ public static void addThreadMetadata(String tabName, String key, Object value) {
+ THREAD_METADATA.get().addMetadata(tabName, key, value);
}
/**
* Clears all metadata added to the current thread
*/
- public static void clearThreadMetaData() {
+ public static void clearThreadMetadata() {
THREAD_METADATA.get().clear();
}
@@ -634,8 +639,8 @@ public static void clearThreadMetaData() {
*
* @param tabName the name of the tab to remove
*/
- public static void clearThreadMetaData(String tabName) {
- THREAD_METADATA.get().clearTab(tabName);
+ public static void clearThreadMetadata(String tabName) {
+ THREAD_METADATA.get().clearMetadata(tabName);
}
/**
@@ -644,8 +649,8 @@ public static void clearThreadMetaData(String tabName) {
* @param tabName the name of the tab to that the metadata is in
* @param key the key of the metadata to remove
*/
- public static void clearThreadMetaData(String tabName, String key) {
- THREAD_METADATA.get().clearKey(tabName, key);
+ public static void clearThreadMetadata(String tabName, String key) {
+ THREAD_METADATA.get().clearMetadata(tabName, key);
}
Configuration getConfig() {
@@ -671,7 +676,7 @@ public static Set uncaughtExceptionClients() {
return Collections.emptySet();
}
- void addBeforeSendSession(BeforeSendSession beforeSendSession) {
- sessionTracker.addBeforeSendSession(beforeSendSession);
+ void addOnSession(OnSession onSession) {
+ sessionTracker.addOnSession(onSession);
}
}
diff --git a/bugsnag/src/main/java/com/bugsnag/BugsnagAppender.java b/bugsnag/src/main/java/com/bugsnag/BugsnagAppender.java
index 5d3cd23d..07f42b56 100644
--- a/bugsnag/src/main/java/com/bugsnag/BugsnagAppender.java
+++ b/bugsnag/src/main/java/com/bugsnag/BugsnagAppender.java
@@ -3,9 +3,9 @@
import com.bugsnag.callbacks.Callback;
import com.bugsnag.delivery.Delivery;
import com.bugsnag.logback.BugsnagMarker;
-import com.bugsnag.logback.LogbackMetaData;
-import com.bugsnag.logback.LogbackMetaDataKey;
-import com.bugsnag.logback.LogbackMetaDataTab;
+import com.bugsnag.logback.LogbackMetadata;
+import com.bugsnag.logback.LogbackMetadataKey;
+import com.bugsnag.logback.LogbackMetadataTab;
import com.bugsnag.logback.ProxyConfiguration;
import ch.qos.logback.classic.Level;
@@ -48,14 +48,14 @@ public class BugsnagAppender extends UnsynchronizedAppenderBase {
/** Bugsnag error server endpoint. */
private String endpoint;
- /** Property names that should be filtered out before sending to Bugsnag servers. */
- private Set filteredProperties = new HashSet();
+ /** Property names that should be redacted before sending to Bugsnag servers. */
+ private Set redactedKeys = new HashSet();
/** Exception classes to be ignored. */
- private Set ignoredClasses = new HashSet();
+ private Set discardClasses = new HashSet();
/** Release stages that should be notified. */
- private Set notifyReleaseStages = new HashSet();
+ private Set enabledReleaseStages = new HashSet();
/** Project packages. */
private Set projectPackages = new HashSet();
@@ -75,7 +75,7 @@ public class BugsnagAppender extends UnsynchronizedAppenderBase {
/** Application version. */
private String appVersion;
- private List globalMetaData = new ArrayList();
+ private List globalMetadata = new ArrayList();
/** Bugsnag client. */
private Bugsnag bugsnag = null;
@@ -134,20 +134,24 @@ protected void append(final ILoggingEvent event) {
calculateSeverity(event),
new Callback() {
@Override
- public void beforeNotify(Report report) {
+ public boolean onError(Report report) {
// Add some data from the logging event
- report.addToTab("Log event data",
+ report.addMetadata("Log event data",
"Message", event.getFormattedMessage());
- report.addToTab("Log event data",
+ report.addMetadata("Log event data",
"Logger name", event.getLoggerName());
// Add details from the logging context to the event
populateContextData(report, event);
if (reportCallback != null) {
- reportCallback.beforeNotify(report);
+ boolean proceed = reportCallback.onError(report);
+ if (!proceed) {
+ return false; // suppress delivery
+ }
}
+ return true;
}
});
}
@@ -167,7 +171,7 @@ private void populateContextData(Report report, ILoggingEvent event) {
// Loop through all the keys and put them in the correct tabs
for (Map.Entry entry : propertyMap.entrySet()) {
- report.addToTab("Context", entry.getKey(), entry.getValue());
+ report.addMetadata("Context", entry.getKey(), entry.getValue());
}
}
}
@@ -254,34 +258,35 @@ private Bugsnag createBugsnag() {
bugsnag.setTimeout(timeout);
}
- if (filteredProperties.size() > 0) {
- bugsnag.setFilters(filteredProperties.toArray(new String[0]));
+ if (!redactedKeys.isEmpty()) {
+ bugsnag.setRedactedKeys(redactedKeys.toArray(new String[0]));
}
- bugsnag.setIgnoreClasses(ignoredClasses.toArray(new String[0]));
+ bugsnag.setDiscardClasses(discardClasses.toArray(new String[0]));
- if (notifyReleaseStages.size() > 0) {
- bugsnag.setNotifyReleaseStages(notifyReleaseStages.toArray(new String[0]));
+ if (!enabledReleaseStages.isEmpty()) {
+ bugsnag.setEnabledReleaseStages(enabledReleaseStages.toArray(new String[0]));
}
bugsnag.setProjectPackages(projectPackages.toArray(new String[0]));
bugsnag.setSendThreads(sendThreads);
- // Add a callback to put global meta data on every report
+ // Add a callback to put global metadata on every report
bugsnag.addCallback(new Callback() {
@Override
- public void beforeNotify(Report report) {
+ public boolean onError(Report report) {
- for (LogbackMetaData metaData : globalMetaData) {
- for (LogbackMetaDataTab tab : metaData.getTabs()) {
- for (LogbackMetaDataKey key : tab.getKeys()) {
- report.addToTab(tab.getName(),
+ for (LogbackMetadata metadata : globalMetadata) {
+ for (LogbackMetadataTab tab : metadata.getTabs()) {
+ for (LogbackMetadataKey key : tab.getKeys()) {
+ report.addMetadata(tab.getName(),
key.getName(),
key.getValue());
}
}
}
+ return true;
}
});
@@ -374,68 +379,78 @@ public void setEndpoint(String endpoint) {
}
/**
- * @see Bugsnag#setFilters(String...)
+ * @see Bugsnag#setRedactedKeys(String...)
*/
- public void setFilteredProperty(String filter) {
- this.filteredProperties.add(filter);
+ public void setRedactedKey(String key) {
+ this.redactedKeys.add(key);
if (bugsnag != null) {
- bugsnag.setFilters(this.filteredProperties.toArray(new String[0]));
+ bugsnag.setRedactedKeys(this.redactedKeys.toArray(new String[0]));
}
}
/**
- * @see Bugsnag#setFilters(String...)
+ * @see Bugsnag#setRedactedKeys(String...)
*/
- public void setFilteredProperties(String filters) {
- this.filteredProperties.addAll(split(filters));
+ public void setRedactedKeys(String key) {
+ this.redactedKeys.addAll(split(key));
if (bugsnag != null) {
- bugsnag.setFilters(this.filteredProperties.toArray(new String[0]));
+ bugsnag.setRedactedKeys(this.redactedKeys.toArray(new String[0]));
}
}
+ @Deprecated
+ public void setIgnoredClass(String ignoredClass) {
+ setDiscardClass(ignoredClass);
+ }
+
/**
- * @see Bugsnag#setIgnoreClasses(String...)
+ * @see Bugsnag#setDiscardClasses(String...)
*/
- public void setIgnoredClass(String ignoredClass) {
- this.ignoredClasses.add(ignoredClass);
+ public void setDiscardClass(String discardClass) {
+ this.discardClasses.add(discardClass);
if (bugsnag != null) {
- bugsnag.setIgnoreClasses(this.ignoredClasses.toArray(new String[0]));
+ bugsnag.setDiscardClasses(this.discardClasses.toArray(new String[0]));
}
}
/**
- * @see Bugsnag#setIgnoreClasses(String...)
+ * @see Bugsnag#setDiscardClasses(String...)
*/
- public void setIgnoredClasses(String ignoredClasses) {
- this.ignoredClasses.addAll(split(ignoredClasses));
+ public void setDiscardClasses(String discardClasses) {
+ this.discardClasses.addAll(split(discardClasses));
if (bugsnag != null) {
- bugsnag.setIgnoreClasses(this.ignoredClasses.toArray(new String[0]));
+ bugsnag.setDiscardClasses(this.discardClasses.toArray(new String[0]));
}
}
+ @Deprecated
+ public void setNotifyReleaseStage(String notifyReleaseStage) {
+ setEnabledReleaseStage(notifyReleaseStage);
+ }
+
/**
- * @see Bugsnag#setNotifyReleaseStages(String...)
+ * @see Bugsnag#setEnabledReleaseStages(String...)
*/
- public void setNotifyReleaseStage(String notifyReleaseStage) {
- this.notifyReleaseStages.add(notifyReleaseStage);
+ public void setEnabledReleaseStage(String enabledReleaseStage) {
+ this.enabledReleaseStages.add(enabledReleaseStage);
if (bugsnag != null) {
- bugsnag.setNotifyReleaseStages(this.notifyReleaseStages.toArray(new String[0]));
+ bugsnag.setEnabledReleaseStages(this.enabledReleaseStages.toArray(new String[0]));
}
}
/**
- * @see Bugsnag#setNotifyReleaseStages(String...)
+ * @see Bugsnag#setEnabledReleaseStages(String...)
*/
- public void setNotifyReleaseStages(String notifyReleaseStages) {
- this.notifyReleaseStages.addAll(split(notifyReleaseStages));
+ public void setEnabledReleaseStages(String enabledReleaseStages) {
+ this.enabledReleaseStages.addAll(split(enabledReleaseStages));
if (bugsnag != null) {
- bugsnag.setNotifyReleaseStages(this.notifyReleaseStages.toArray(new String[0]));
+ bugsnag.setEnabledReleaseStages(this.enabledReleaseStages.toArray(new String[0]));
}
}
@@ -524,10 +539,15 @@ public void setAppVersion(String appVersion) {
* Internal use only
* Should only be used via the logback.xml file
*
- * @param metaData Adds meta data to every report
+ * @param metadata Adds metadata to every report
*/
- public void setMetaData(LogbackMetaData metaData) {
- this.globalMetaData.add(metaData);
+ public void setMetadata(LogbackMetadata metadata) {
+ this.globalMetadata.add(metadata);
+ }
+
+ @Deprecated
+ public void setMetaData(LogbackMetadata metadata) {
+ setMetadata(metadata);
}
/**
diff --git a/bugsnag/src/main/java/com/bugsnag/Configuration.java b/bugsnag/src/main/java/com/bugsnag/Configuration.java
index 351c1c0d..7fd5d536 100644
--- a/bugsnag/src/main/java/com/bugsnag/Configuration.java
+++ b/bugsnag/src/main/java/com/bugsnag/Configuration.java
@@ -4,7 +4,6 @@
import com.bugsnag.callbacks.Callback;
import com.bugsnag.callbacks.DeviceCallback;
import com.bugsnag.callbacks.JakartaServletCallback;
-import com.bugsnag.callbacks.JavaxServletCallback;
import com.bugsnag.delivery.AsyncHttpDelivery;
import com.bugsnag.delivery.Delivery;
import com.bugsnag.delivery.HttpDelivery;
@@ -20,6 +19,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -31,19 +31,19 @@ public class Configuration {
private static final String HEADER_API_KEY = "Bugsnag-Api-Key";
private static final String HEADER_BUGSNAG_SENT_AT = "Bugsnag-Sent-At";
- public String apiKey;
- public String appType;
- public String appVersion;
- public Delivery delivery;
- public EndpointConfiguration endpointConfiguration;
- public Delivery sessionDelivery;
- public String[] filters = new String[]{"password", "secret", "Authorization", "Cookie"};
- public String[] ignoreClasses;
- public String[] notifyReleaseStages = null;
- public String[] projectPackages;
- public String releaseStage;
- public boolean sendThreads = false;
- public Serializer serializer = new DefaultSerializer();
+ private String apiKey;
+ private String appType;
+ private String appVersion;
+ private Delivery delivery;
+ private EndpointConfiguration endpoints;
+ private Delivery sessionDelivery;
+ private String[] redactedKeys = new String[] {"password", "secret", "Authorization", "Cookie"};
+ private String[] discardClasses;
+ private Set enabledReleaseStages = null;
+ private String[] projectPackages;
+ private String releaseStage;
+ private boolean sendThreads = false;
+ private Serializer serializer = new DefaultSerializer();
Collection callbacks = new ConcurrentLinkedQueue();
private final AtomicBoolean autoCaptureSessions = new AtomicBoolean(true);
@@ -56,14 +56,10 @@ public class Configuration {
addCallback(new DeviceCallback());
DeviceCallback.initializeCache();
- endpointConfiguration = EndpointConfiguration.fromApiKey(apiKey);
+ endpoints = EndpointConfiguration.fromApiKey(apiKey);
- this.delivery = new AsyncHttpDelivery(endpointConfiguration.getNotifyEndpoint());
- this.sessionDelivery = new AsyncHttpDelivery(endpointConfiguration.getSessionEndpoint());
-
- if (JavaxServletCallback.isAvailable()) {
- addCallback(new JavaxServletCallback());
- }
+ this.delivery = new AsyncHttpDelivery(endpoints.getNotifyEndpoint());
+ this.sessionDelivery = new AsyncHttpDelivery(endpoints.getSessionEndpoint());
if (JakartaServletCallback.isAvailable()) {
addCallback(new JakartaServletCallback());
@@ -71,20 +67,18 @@ public class Configuration {
}
boolean shouldNotifyForReleaseStage() {
- if (notifyReleaseStages == null) {
+ if (enabledReleaseStages == null) {
return true;
}
-
- List stages = Arrays.asList(notifyReleaseStages);
- return stages.contains(releaseStage);
+ return enabledReleaseStages.contains(releaseStage);
}
boolean shouldIgnoreClass(String className) {
- if (ignoreClasses == null) {
+ if (discardClasses == null) {
return false;
}
- List classes = Arrays.asList(ignoreClasses);
+ List classes = Arrays.asList(discardClasses);
return classes.contains(className);
}
@@ -134,15 +128,19 @@ public void setEndpoints(String notify, String sessions) throws IllegalArgumentE
* Set the endpoints to send data to. Use this to override the default endpoints
* if you are using Bugsnag Enterprise to point to your own Bugsnag endpoint.
*
- * Please note that it is recommended that you set both endpoints. If the notify endpoint is
- * missing, an exception will be thrown. If the session endpoint is missing, a warning will be
+ * Please note that it is recommended that you set both endpoints. If the notify
+ * endpoint is
+ * missing, an exception will be thrown. If the session endpoint is missing, a
+ * warning will be
* logged and sessions will not be sent automatically.
*
- * Note that if you are setting a custom {@link Delivery}, this method should be called after
+ * Note that if you are setting a custom {@link Delivery}, this method should be
+ * called after
* the custom implementation has been set.
*
* @param endpointConfiguration the endpoint configuration
- * @throws IllegalArgumentException if the endpoint configuration is null or if the notify endpoint is empty or null
+ * @throws IllegalArgumentException if the endpoint configuration is null or if
+ * the notify endpoint is empty or null
*/
public void setEndpoints(EndpointConfiguration endpointConfiguration) throws IllegalArgumentException {
if (endpointConfiguration == null) {
@@ -196,4 +194,109 @@ Map getSessionApiHeaders() {
map.put(HEADER_BUGSNAG_SENT_AT, DateUtils.toIso8601(new Date()));
return map;
}
+
+ // Accessors
+ public String getApiKey() {
+ return apiKey;
+ }
+
+ public void setApiKey(String apiKey) {
+ this.apiKey = apiKey;
+ }
+
+ public String getAppType() {
+ return appType;
+ }
+
+ public void setAppType(String appType) {
+ this.appType = appType;
+ }
+
+ public String getAppVersion() {
+ return appVersion;
+ }
+
+ public void setAppVersion(String appVersion) {
+ this.appVersion = appVersion;
+ }
+
+ public Delivery getDelivery() {
+ return delivery;
+ }
+
+ public void setDelivery(Delivery delivery) {
+ this.delivery = delivery;
+ }
+
+ public EndpointConfiguration getEndpointsConfiguration() {
+ return endpoints;
+ }
+
+ public void setEndpointsConfiguration(EndpointConfiguration endpoints) {
+ this.endpoints = endpoints;
+ }
+
+ public Delivery getSessionDelivery() {
+ return sessionDelivery;
+ }
+
+ public void setSessionDelivery(Delivery sessionDelivery) {
+ this.sessionDelivery = sessionDelivery;
+ }
+
+ public String[] getRedactedKeys() {
+ return redactedKeys;
+ }
+
+ public void setRedactedKeys(String[] redactedKeys) {
+ this.redactedKeys = redactedKeys;
+ }
+
+ public String[] getDiscardClasses() {
+ return discardClasses;
+ }
+
+ public void setDiscardClasses(String[] discardClasses) {
+ this.discardClasses = discardClasses;
+ }
+
+ public Set getEnabledReleaseStages() {
+ return enabledReleaseStages;
+ }
+
+ public void setEnabledReleaseStages(Set enabledReleaseStages) {
+ this.enabledReleaseStages = enabledReleaseStages;
+ }
+
+ public String[] getProjectPackages() {
+ return projectPackages;
+ }
+
+ public void setProjectPackages(String[] projectPackages) {
+ this.projectPackages = projectPackages;
+ }
+
+ public String getReleaseStage() {
+ return releaseStage;
+ }
+
+ public void setReleaseStage(String releaseStage) {
+ this.releaseStage = releaseStage;
+ }
+
+ public boolean isSendThreads() {
+ return sendThreads;
+ }
+
+ public void setSendThreads(boolean sendThreads) {
+ this.sendThreads = sendThreads;
+ }
+
+ public Serializer getSerializer() {
+ return serializer;
+ }
+
+ public void setSerializer(Serializer serializer) {
+ this.serializer = serializer;
+ }
}
diff --git a/bugsnag/src/main/java/com/bugsnag/Diagnostics.java b/bugsnag/src/main/java/com/bugsnag/Diagnostics.java
index d6aa3e1a..6164e680 100644
--- a/bugsnag/src/main/java/com/bugsnag/Diagnostics.java
+++ b/bugsnag/src/main/java/com/bugsnag/Diagnostics.java
@@ -11,7 +11,7 @@ class Diagnostics {
Map app;
Map device;
Map user = new HashMap();
- MetaData metaData = new MetaData();
+ Metadata metadata = new Metadata();
Diagnostics(Configuration configuration) {
app = getDefaultAppInfo(configuration);
@@ -37,11 +37,11 @@ private Map getRuntimeVersions() {
private Map getDefaultAppInfo(Configuration configuration) {
Map map = new HashMap();
- if (configuration.releaseStage != null) {
- map.put("releaseStage", configuration.releaseStage);
+ if (configuration.getReleaseStage() != null) {
+ map.put("releaseStage", configuration.getReleaseStage());
}
- if (configuration.appVersion != null) {
- map.put("version", configuration.appVersion);
+ if (configuration.getAppVersion() != null) {
+ map.put("version", configuration.getAppVersion());
}
return map;
}
diff --git a/bugsnag/src/main/java/com/bugsnag/MetaData.java b/bugsnag/src/main/java/com/bugsnag/MetaData.java
deleted file mode 100644
index 33d215fd..00000000
--- a/bugsnag/src/main/java/com/bugsnag/MetaData.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.bugsnag;
-
-import java.util.HashMap;
-import java.util.Map;
-
-class MetaData extends HashMap {
- private static final long serialVersionUID = 2530038179702722770L;
-
- public void addToTab(String tabName, String key, Object value) {
- Map tab = getTab(tabName);
- tab.put(key, value);
- }
-
- void clearTab(String tabName) {
- remove(tabName);
- }
-
- void clearKey(String tabName, String key) {
- Map tab = getTab(tabName);
- tab.remove(key);
- }
-
- void merge(MetaData metaData) {
- for (String tabName : metaData.keySet()) {
- getTab(tabName).putAll(metaData.getTab(tabName));
- }
- }
-
- @SuppressWarnings(value = "unchecked")
- private Map getTab(String tabName) {
- Map tab = (Map) get(tabName);
- if (tab == null) {
- tab = new HashMap();
- put(tabName, tab);
- }
-
- return tab;
- }
-}
diff --git a/bugsnag/src/main/java/com/bugsnag/Metadata.java b/bugsnag/src/main/java/com/bugsnag/Metadata.java
new file mode 100644
index 00000000..cd69958c
--- /dev/null
+++ b/bugsnag/src/main/java/com/bugsnag/Metadata.java
@@ -0,0 +1,39 @@
+package com.bugsnag;
+
+import java.util.HashMap;
+import java.util.Map;
+
+class Metadata extends HashMap {
+ private static final long serialVersionUID = 2530038179702722770L;
+
+ public void addMetadata(String tabName, String key, Object value) {
+ Map tab = getMetadata(tabName);
+ tab.put(key, value);
+ }
+
+ void clearMetadata(String tabName) {
+ remove(tabName);
+ }
+
+ void clearMetadata(String tabName, String key) {
+ Map tab = getMetadata(tabName);
+ tab.remove(key);
+ }
+
+ void merge(Metadata metadata) {
+ for (String tabName : metadata.keySet()) {
+ getMetadata(tabName).putAll(metadata.getMetadata(tabName));
+ }
+ }
+
+ @SuppressWarnings(value = "unchecked")
+ private Map getMetadata(String tabName) {
+ Map tab = (Map) get(tabName);
+ if (tab == null) {
+ tab = new HashMap();
+ put(tabName, tab);
+ }
+
+ return tab;
+ }
+}
diff --git a/bugsnag/src/main/java/com/bugsnag/Notification.java b/bugsnag/src/main/java/com/bugsnag/Notification.java
index eed27ce6..62b4f0db 100644
--- a/bugsnag/src/main/java/com/bugsnag/Notification.java
+++ b/bugsnag/src/main/java/com/bugsnag/Notification.java
@@ -17,7 +17,7 @@ class Notification {
@Expose
public String getApiKey() {
String reportApiKey = report.getApiKey();
- return reportApiKey != null ? reportApiKey : config.apiKey;
+ return reportApiKey != null ? reportApiKey : config.getApiKey();
}
@Expose
diff --git a/bugsnag/src/main/java/com/bugsnag/OnSession.java b/bugsnag/src/main/java/com/bugsnag/OnSession.java
new file mode 100644
index 00000000..b3349f54
--- /dev/null
+++ b/bugsnag/src/main/java/com/bugsnag/OnSession.java
@@ -0,0 +1,6 @@
+package com.bugsnag;
+
+@FunctionalInterface
+interface OnSession {
+ boolean onSession(SessionPayload payload);
+}
diff --git a/bugsnag/src/main/java/com/bugsnag/util/FilteredMap.java b/bugsnag/src/main/java/com/bugsnag/RedactedMap.java
similarity index 55%
rename from bugsnag/src/main/java/com/bugsnag/util/FilteredMap.java
rename to bugsnag/src/main/java/com/bugsnag/RedactedMap.java
index 1adc9269..b417137d 100644
--- a/bugsnag/src/main/java/com/bugsnag/util/FilteredMap.java
+++ b/bugsnag/src/main/java/com/bugsnag/RedactedMap.java
@@ -1,24 +1,24 @@
-package com.bugsnag.util;
+package com.bugsnag;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
- * Decorates a map by replacing values of filtered keys.
+ * Decorates a map by replacing values of redacted keys.
*/
-public class FilteredMap implements Map {
+class RedactedMap implements Map {
- private static final String FILTERED_PLACEHOLDER = "[FILTERED]";
+ private static final String REDACTED_PLACEHOLDER = "[REDACTED]";
- private final Map filteredCopy;
- private final Collection keyFilters = new ArrayList();
+ private final Map redactedCopy;
+ private final Collection redactedKeys = new HashSet<>();
- public FilteredMap(Map map, Collection keyFilters) {
- this.keyFilters.addAll(keyFilters);
- this.filteredCopy = createCopy(map);
+ RedactedMap(Map map, Collection redactedKeys) {
+ this.redactedKeys.addAll(redactedKeys);
+ this.redactedCopy = createCopy(map);
}
private Map createCopy(Map extends String, ?> map) {
@@ -36,84 +36,87 @@ private Map createCopy(Map extends String, ?> map) {
@Override
public int size() {
- return filteredCopy.size();
+ return redactedCopy.size();
}
@Override
public boolean isEmpty() {
- return filteredCopy.isEmpty();
+ return redactedCopy.isEmpty();
}
@Override
public boolean containsKey(Object key) {
- return filteredCopy.containsKey(key);
+ return redactedCopy.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
- return filteredCopy.containsValue(value);
+ return redactedCopy.containsValue(value);
}
@Override
public Object get(Object key) {
- return filteredCopy.get(key);
+ return redactedCopy.get(key);
}
@Override
public Object put(String key, Object value) {
if (value == null) {
- return filteredCopy.put(key, null);
+ return redactedCopy.put(key, null);
}
Object transformedValue = transformEntry(key, value);
- return filteredCopy.put(key, transformedValue);
+ return redactedCopy.put(key, transformedValue);
}
@Override
public Object remove(Object key) {
- return filteredCopy.remove(key);
+ return redactedCopy.remove(key);
}
@Override
public void putAll(Map extends String, ?> mapValues) {
Map copy = createCopy(mapValues);
- filteredCopy.putAll(copy);
+ redactedCopy.putAll(copy);
}
@Override
public void clear() {
- filteredCopy.clear();
+ redactedCopy.clear();
}
@Override
public Set keySet() {
- return filteredCopy.keySet();
+ return redactedCopy.keySet();
}
@Override
public Collection