From b8574b9ad78bdc677cd5abe856fd80fb2e81a605 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Fri, 6 Jan 2023 09:23:28 +0000 Subject: [PATCH 01/35] [PLAT-9300] Bump jackson-databind dep from 2.13.3 to 2.14.1 --- bugsnag/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bugsnag/build.gradle b/bugsnag/build.gradle index aa2ae9e3..3c81c924 100644 --- a/bugsnag/build.gradle +++ b/bugsnag/build.gradle @@ -10,7 +10,7 @@ repositories { } dependencies { - compile "com.fasterxml.jackson.core:jackson-databind:2.13.3" + compile "com.fasterxml.jackson.core:jackson-databind:2.14.1" compile "org.slf4j:slf4j-api:1.7.25" compileOnly "javax.servlet:javax.servlet-api:${servletApiVersion}" compileOnly("ch.qos.logback:logback-classic:${logbackVersion}") { From 0090efec3b9442d03625d6486f1ad181082e8b6b Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Fri, 6 Jan 2023 09:59:27 +0000 Subject: [PATCH 02/35] update CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 572dbb93..7c775499 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## TBD + +* Bump Jackson from 2.13.3 for critical vulnerability fixes + [#184](https://github.com/bugsnag/bugsnag-java/pull/184) + ## 3.6.4 (2022-07-12) * Support log messages that use `{}` formatting From df213be599878ada0a6c3e8e5335be5ced98b415 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Fri, 6 Jan 2023 16:01:40 +0000 Subject: [PATCH 03/35] Update maze-runner to 7.10.2 --- Gemfile | 2 +- Gemfile.lock | 77 +++++++++++++++++++++++++++++++--------------------- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/Gemfile b/Gemfile index 7af6d244..6f2460eb 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', tag: 'v6.8.0' +gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', tag: 'v7.10.2' gem 'os' diff --git a/Gemfile.lock b/Gemfile.lock index f1fe1b94..ed77d7fa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,37 +1,39 @@ GIT remote: https://github.com/bugsnag/maze-runner - revision: fe12189f83aad154f54221ee0fcd41b483d3c0d1 - tag: v6.8.0 + revision: 734405a801012be706f5e190e472fb02dd83b264 + tag: v7.10.2 specs: - bugsnag-maze-runner (6.8.0) - appium_lib (~> 11.2.0) + bugsnag-maze-runner (7.10.2) + appium_lib (~> 12.0.0) + appium_lib_core (~> 5.4.0) bugsnag (~> 6.24) cucumber (~> 7.1) cucumber-expressions (~> 6.0.0) curb (~> 0.9.6) + json_schemer (~> 0.2.24) optimist (~> 3.0.1) os (~> 1.0.0) rake (~> 12.3.3) rubyzip (~> 2.3.2) - selenium-webdriver (~> 3.11) + selenium-webdriver (~> 4.0) test-unit (~> 3.5.2) webrick (~> 1.7.0) GEM remote: https://rubygems.org/ specs: - appium_lib (11.2.0) - appium_lib_core (~> 4.1) + appium_lib (12.0.1) + appium_lib_core (~> 5.0) nokogiri (~> 1.8, >= 1.8.1) - tomlrb (~> 1.1) - appium_lib_core (4.7.1) + tomlrb (>= 1.1, < 3.0) + appium_lib_core (5.4.0) faye-websocket (~> 0.11.0) - selenium-webdriver (~> 3.14, >= 3.14.1) - bugsnag (6.24.1) + selenium-webdriver (~> 4.2, < 4.6) + bugsnag (6.25.1) concurrent-ruby (~> 1.0) builder (3.2.4) - childprocess (3.0.0) - concurrent-ruby (1.1.9) + childprocess (4.1.0) + concurrent-ruby (1.1.10) cucumber (7.1.0) builder (~> 3.2, >= 3.2.4) cucumber-core (~> 10.1, >= 10.1.0) @@ -45,10 +47,10 @@ GEM mime-types (~> 3.3, >= 3.3.1) multi_test (~> 0.1, >= 0.1.2) sys-uname (~> 1.2, >= 1.2.2) - cucumber-core (10.1.0) + cucumber-core (10.1.1) cucumber-gherkin (~> 22.0, >= 22.0.0) cucumber-messages (~> 17.1, >= 17.1.1) - cucumber-tag-expressions (~> 4.0, >= 4.0.2) + cucumber-tag-expressions (~> 4.1, >= 4.1.0) cucumber-create-meta (6.0.4) cucumber-messages (~> 17.1, >= 17.1.1) sys-uname (~> 1.2, >= 1.2.2) @@ -60,40 +62,53 @@ GEM cucumber-messages (~> 17.1, >= 17.1.0) cucumber-messages (17.1.1) cucumber-tag-expressions (4.1.0) - cucumber-wire (6.2.0) + cucumber-wire (6.2.1) cucumber-core (~> 10.1, >= 10.1.0) cucumber-cucumber-expressions (~> 14.0, >= 14.0.0) - cucumber-messages (~> 17.1, >= 17.1.1) curb (0.9.11) diff-lcs (1.5.0) + ecma-re-validator (0.4.0) + regexp_parser (~> 2.2) eventmachine (1.2.7) faye-websocket (0.11.1) eventmachine (>= 0.12.0) websocket-driver (>= 0.5.1) - ffi (1.15.4) + ffi (1.15.5) + hana (1.3.7) + json_schemer (0.2.24) + ecma-re-validator (~> 0.3) + hana (~> 1.3) + regexp_parser (~> 2.0) + uri_template (~> 0.7) mime-types (3.4.1) mime-types-data (~> 3.2015) - mime-types-data (3.2021.1115) - mini_portile2 (2.6.1) + mime-types-data (3.2022.0105) + mini_portile2 (2.8.1) multi_test (0.1.2) - nokogiri (1.12.5) - mini_portile2 (~> 2.6.1) + nokogiri (1.13.10) + mini_portile2 (~> 2.8.0) racc (~> 1.4) optimist (3.0.1) - os (1.0.0) - power_assert (2.0.1) - racc (1.6.0) + os (1.0.1) + power_assert (2.0.3) + racc (1.6.2) rake (12.3.3) + regexp_parser (2.6.1) + rexml (3.2.5) rubyzip (2.3.2) - selenium-webdriver (3.142.7) - childprocess (>= 0.5, < 4.0) - rubyzip (>= 1.2.2) + selenium-webdriver (4.5.0) + childprocess (>= 0.5, < 5.0) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) sys-uname (1.2.2) ffi (~> 1.1) - test-unit (3.5.3) + test-unit (3.5.7) power_assert - tomlrb (1.3.0) + tomlrb (2.0.3) + uri_template (0.7.0) webrick (1.7.0) + websocket (1.2.9) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -106,4 +121,4 @@ DEPENDENCIES os BUNDLED WITH - 1.16.5 + 2.1.4 From ff9c1ecde6bbed9cb2e1565234b008685aaee62c Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Fri, 6 Jan 2023 16:01:58 +0000 Subject: [PATCH 04/35] Bump ruby version to 2.7.5 --- .ruby-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ruby-version b/.ruby-version index 005119ba..a603bb50 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.4.1 +2.7.5 From 8f9f6668e306d43359ffd6743d6d689da481d1d5 Mon Sep 17 00:00:00 2001 From: David Petran Date: Thu, 16 Mar 2023 12:26:19 +0000 Subject: [PATCH 05/35] upgrade gradle to v7.6.1 --- bugsnag-spring/build.gradle | 21 ++++++++------ .../bugsnag/BugsnagSpringConfiguration.java | 10 +++---- .../java/com/bugsnag/MvcConfiguration.java | 9 +++--- .../test/java/com/bugsnag/SpringMvcTest.java | 2 +- .../src/test/java/com/bugsnag/TestUtils.java | 6 +--- bugsnag/build.gradle | 28 ++++++++----------- common.gradle | 3 -- dockerfiles/Dockerfile.java-common | 2 +- dockerfiles/Dockerfile.license-audit | 2 +- examples/logback/build.gradle | 4 +-- examples/servlet/build.gradle | 9 +++--- examples/simple/build.gradle | 4 +-- examples/spring-web/build.gradle | 6 ++-- examples/spring/build.gradle | 6 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- 15 files changed, 52 insertions(+), 62 deletions(-) diff --git a/bugsnag-spring/build.gradle b/bugsnag-spring/build.gradle index 786ac088..7bc443d6 100644 --- a/bugsnag-spring/build.gradle +++ b/bugsnag-spring/build.gradle @@ -1,27 +1,32 @@ ext { - springVersion = '4.3.18.RELEASE' - springBootVersion = '1.5.15.RELEASE' + springVersion = '5.3.20' + springBootVersion = '2.5.14' } apply plugin: 'java-library' apply from: '../common.gradle' +sourceCompatibility = '1.8' +targetCompatibility = '1.8' + repositories { mavenCentral() } dependencies { - compile project(':bugsnag') - testCompile project(path: ':bugsnag', configuration: 'testRuntime') + api project(':bugsnag') + testImplementation project(':bugsnag').sourceSets.test.output compileOnly "javax.servlet:javax.servlet-api:${servletApiVersion}" 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:1.7.25" - testCompile "junit:junit:4.13.2" - testCompile "javax.servlet:javax.servlet-api:${servletApiVersion}" - testCompile "org.springframework.boot:spring-boot-starter-test:${springBootVersion}" - testCompile "org.springframework.boot:spring-boot-starter-web:${springBootVersion}" + testImplementation "junit:junit:4.13.2" + testImplementation "javax.servlet:javax.servlet-api:${servletApiVersion}" + testImplementation "org.springframework.boot:spring-boot-starter-test:${springBootVersion}" + testImplementation "org.springframework.boot:spring-boot-starter-web:${springBootVersion}" + testImplementation "org.mockito:mockito-core:5.0.0" } diff --git a/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java index 36fd5ea6..c10e50ae 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java +++ b/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java @@ -2,6 +2,7 @@ import com.bugsnag.callbacks.Callback; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -9,7 +10,6 @@ import org.springframework.core.SpringVersion; import java.util.Map; -import javax.annotation.PostConstruct; /** * Configuration to integrate Bugsnag with Spring. @@ -19,7 +19,7 @@ SpringBootConfiguration.class, MvcConfiguration.class, ScheduledTaskConfiguration.class}) -public class BugsnagSpringConfiguration { +public class BugsnagSpringConfiguration implements InitializingBean { @Autowired private Bugsnag bugsnag; @@ -64,9 +64,8 @@ ScheduledTaskBeanLocator scheduledTaskBeanLocator() { * If using Logback, stop any configured appender from creating Bugsnag reports for Spring log * messages as they effectively duplicate error reports for unhandled exceptions. */ - @PostConstruct - @SuppressWarnings("checkstyle:emptycatchblock") - void excludeLoggers() { + @Override + public void afterPropertiesSet() throws java.lang.Exception { try { // Exclude Tomcat logger when processing HTTP requests via a servlet. // Regex specified to match the servlet variable parts of the logger name, e.g. @@ -86,5 +85,4 @@ void excludeLoggers() { // logback was not in classpath, ignore throwable to allow further initialisation } } - } diff --git a/bugsnag-spring/src/main/java/com/bugsnag/MvcConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/MvcConfiguration.java index f0f27c2f..6d030120 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/MvcConfiguration.java +++ b/bugsnag-spring/src/main/java/com/bugsnag/MvcConfiguration.java @@ -1,18 +1,17 @@ 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; -import javax.annotation.PostConstruct; - /** * If spring-webmvc is loaded, add configuration for reporting unhandled exceptions. */ @Configuration @Conditional(SpringWebMvcLoadedCondition.class) -class MvcConfiguration { +class MvcConfiguration implements InitializingBean { @Autowired private Bugsnag bugsnag; @@ -29,8 +28,8 @@ BugsnagMvcExceptionHandler bugsnagHandlerExceptionResolver() { /** * Add a callback to assign specified severities for some Spring exceptions. */ - @PostConstruct - void addExceptionClassCallback() { + @Override + public void afterPropertiesSet() throws java.lang.Exception { bugsnag.addCallback(new ExceptionClassCallback()); } } diff --git a/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java b/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java index 694bb98b..6c84bbc6 100644 --- a/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java +++ b/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java @@ -25,10 +25,10 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootVersion; -import org.springframework.boot.context.embedded.LocalServerPort; 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; diff --git a/bugsnag-spring/src/test/java/com/bugsnag/TestUtils.java b/bugsnag-spring/src/test/java/com/bugsnag/TestUtils.java index d0f5506f..4bc70115 100644 --- a/bugsnag-spring/src/test/java/com/bugsnag/TestUtils.java +++ b/bugsnag-spring/src/test/java/com/bugsnag/TestUtils.java @@ -27,11 +27,7 @@ static Report verifyAndGetReport(Delivery delivery) { return notificationCaptor.getValue().getEvents().get(0); } - /** - * {@link ArgumentMatchers#anyMapOf} is deprecated but we still need it for JDK 7 builds - */ - @SuppressWarnings("deprecation") static Map anyMapOf(Class keyClazz, Class valueClazz) { - return ArgumentMatchers.anyMapOf(keyClazz, valueClazz); + return ArgumentMatchers.anyMap(); } } diff --git a/bugsnag/build.gradle b/bugsnag/build.gradle index aa2ae9e3..82d1a466 100644 --- a/bugsnag/build.gradle +++ b/bugsnag/build.gradle @@ -1,40 +1,34 @@ plugins { - id "com.github.hierynomus.license" version "0.15.0" + id "com.github.hierynomus.license" version "0.16.1" } apply plugin: 'java-library' apply from: '../common.gradle' +sourceCompatibility = 1.7 +targetCompatibility = 1.7 + repositories { mavenCentral() } dependencies { - compile "com.fasterxml.jackson.core:jackson-databind:2.13.3" - compile "org.slf4j:slf4j-api:1.7.25" + api "com.fasterxml.jackson.core:jackson-databind:2.13.3" + api "org.slf4j:slf4j-api:1.7.25" compileOnly "javax.servlet:javax.servlet-api:${servletApiVersion}" compileOnly("ch.qos.logback:logback-classic:${logbackVersion}") { exclude group: "org.slf4j" } - testCompile "junit:junit:4.13.2" - testCompile "org.slf4j:log4j-over-slf4j:1.7.25" - testCompile "javax.servlet:javax.servlet-api:${servletApiVersion}" - testCompile "org.mockito:mockito-core:2.10.0" - testCompile("ch.qos.logback:logback-classic:${logbackVersion}") { + testImplementation "junit:junit:4.13.2" + testImplementation "org.slf4j:log4j-over-slf4j:1.7.25" + testImplementation "javax.servlet:javax.servlet-api:${servletApiVersion}" + testImplementation "org.mockito:mockito-core:5.0.0" + testImplementation("ch.qos.logback:logback-classic:${logbackVersion}") { exclude group: "org.slf4j" } } -task testJar(type: Jar) { - classifier = 'test' - from sourceSets.test.output -} - -artifacts { - testRuntime testJar -} - // license checking license { header rootProject.file('LICENSE') diff --git a/common.gradle b/common.gradle index fb779f1b..bfacccf4 100644 --- a/common.gradle +++ b/common.gradle @@ -11,9 +11,6 @@ if (project.hasProperty('releasing')) { apply from: "../release.gradle" } -sourceCompatibility = 1.6 -targetCompatibility = 1.6 - test { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" diff --git a/dockerfiles/Dockerfile.java-common b/dockerfiles/Dockerfile.java-common index 9ed9fa29..31b27d5e 100644 --- a/dockerfiles/Dockerfile.java-common +++ b/dockerfiles/Dockerfile.java-common @@ -1,4 +1,4 @@ -FROM openjdk:8 +FROM openjdk:17-jdk-slim WORKDIR /app # Force download of gradle zip early to avoid repeating diff --git a/dockerfiles/Dockerfile.license-audit b/dockerfiles/Dockerfile.license-audit index ab640982..c94c939f 100644 --- a/dockerfiles/Dockerfile.license-audit +++ b/dockerfiles/Dockerfile.license-audit @@ -1,4 +1,4 @@ -FROM openjdk:8-jdk-slim +FROM openjdk:17-jdk-slim RUN apt-get update RUN apt-get install -y ruby-full curl diff --git a/examples/logback/build.gradle b/examples/logback/build.gradle index 6cc4cb94..cbc22ae0 100644 --- a/examples/logback/build.gradle +++ b/examples/logback/build.gradle @@ -11,8 +11,8 @@ repositories { } dependencies { - compile("ch.qos.logback:logback-classic:1.2.3") - compile project(':bugsnag') + implementation "ch.qos.logback:logback-classic:1.2.3" + implementation project(':bugsnag') } mainClassName = "com.bugsnag.example.logback.cli.Application" diff --git a/examples/servlet/build.gradle b/examples/servlet/build.gradle index fe158c0e..1e4412f3 100644 --- a/examples/servlet/build.gradle +++ b/examples/servlet/build.gradle @@ -1,6 +1,6 @@ apply plugin: 'java' apply plugin: 'war' -apply plugin: 'org.akhikhl.gretty' +apply plugin: 'org.gretty' buildscript { repositories { @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'org.akhikhl.gretty:gretty:+' + classpath 'org.gretty:gretty:3.1.1' } } @@ -17,8 +17,9 @@ repositories { } dependencies { - runtime 'org.slf4j:slf4j-simple:1.7.25' - compile project(':bugsnag') + runtimeOnly 'org.slf4j:slf4j-simple:1.7.25' + implementation "javax.servlet:javax.servlet-api:3.1.0" + implementation project(':bugsnag') } gretty { diff --git a/examples/simple/build.gradle b/examples/simple/build.gradle index f4d0b027..e9f1ebe7 100644 --- a/examples/simple/build.gradle +++ b/examples/simple/build.gradle @@ -5,8 +5,8 @@ repositories { } dependencies { - runtime 'org.slf4j:slf4j-simple:1.+' - compile project(':bugsnag') + runtimeOnly 'org.slf4j:slf4j-simple:1.+' + implementation project(':bugsnag') } mainClassName = "com.bugsnag.example.simple.ExampleApp" diff --git a/examples/spring-web/build.gradle b/examples/spring-web/build.gradle index 360beca9..ef6c3b41 100644 --- a/examples/spring-web/build.gradle +++ b/examples/spring-web/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - springBootVersion = '2.1.1.RELEASE' + springBootVersion = '2.5.14' } repositories { mavenCentral() @@ -19,8 +19,8 @@ repositories { } dependencies { - compile("org.springframework.boot:spring-boot-starter-web") + implementation "org.springframework.boot:spring-boot-starter-web" - compile project(":bugsnag-spring") + implementation project(":bugsnag-spring") } diff --git a/examples/spring/build.gradle b/examples/spring/build.gradle index aa986c0f..65d62904 100644 --- a/examples/spring/build.gradle +++ b/examples/spring/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - springBootVersion = '2.1.1.RELEASE' + springBootVersion = '2.5.14' } repositories { mavenCentral() @@ -19,7 +19,7 @@ repositories { } dependencies { - compile("org.springframework.boot:spring-boot-starter") + implementation "org.springframework.boot:spring-boot-starter" - compile project(":bugsnag-spring") + implementation project(":bugsnag-spring") } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1ddc1cb0..05376a7d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip From 5d3eee5f4899e9116f10311fee54b58e11f257dd Mon Sep 17 00:00:00 2001 From: David Petran Date: Thu, 16 Mar 2023 13:32:35 +0000 Subject: [PATCH 06/35] make maze runner gradle files compatible with v7 --- features/fixtures/mazerunner/build.gradle | 16 +++++++-------- .../mazerunnerplainspring/build.gradle | 16 +++++++-------- .../mazerunnerspringboot/build.gradle | 20 +++++++++---------- features/fixtures/scenarios/build.gradle | 4 ++-- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/features/fixtures/mazerunner/build.gradle b/features/fixtures/mazerunner/build.gradle index 235886a6..f922c942 100644 --- a/features/fixtures/mazerunner/build.gradle +++ b/features/fixtures/mazerunner/build.gradle @@ -17,7 +17,7 @@ apply plugin: 'io.spring.dependency-management' group 'com.bugsnag.mazerunner' version '1.0-SNAPSHOT' -sourceCompatibility = 1.7 +sourceCompatibility = 1.8 repositories { mavenCentral() @@ -27,14 +27,14 @@ repositories { } dependencies { - compile("org.springframework.boot:spring-boot-starter") - compile("ch.qos.logback:logback-classic:1.2.3") + implementation("org.springframework.boot:spring-boot-starter") + implementation("ch.qos.logback:logback-classic:1.2.3") - compile("com.fasterxml.jackson.core:jackson-annotations:2.9.1") - compile("com.fasterxml.jackson.core:jackson-databind:2.9.1") - compile("com.bugsnag:bugsnag:9.9.9-test") - compile project(":scenarios") + implementation("com.fasterxml.jackson.core:jackson-annotations:2.9.1") + implementation("com.fasterxml.jackson.core:jackson-databind:2.9.1") + implementation("com.bugsnag:bugsnag:9.9.9-test") + implementation project(":scenarios") - testCompile group: 'junit', name: 'junit', version: '4.13.2' + testImplementation group: 'junit', name: 'junit', version: '4.13.2' } diff --git a/features/fixtures/mazerunnerplainspring/build.gradle b/features/fixtures/mazerunnerplainspring/build.gradle index 1fcc2fe5..ddce65c6 100644 --- a/features/fixtures/mazerunnerplainspring/build.gradle +++ b/features/fixtures/mazerunnerplainspring/build.gradle @@ -14,15 +14,15 @@ repositories { } dependencies { - compile("org.springframework:spring-webmvc:4.2.0.RELEASE") - compile("javax.servlet:javax.servlet-api:3.1.0") - compile("ch.qos.logback:logback-classic:1.2.3") + implementation("org.springframework:spring-webmvc:4.2.0.RELEASE") + implementation("javax.servlet:javax.servlet-api:3.1.0") + implementation("ch.qos.logback:logback-classic:1.2.3") - compile("com.fasterxml.jackson.core:jackson-annotations:2.9.1") - compile("com.fasterxml.jackson.core:jackson-databind:2.9.1") - compile("com.bugsnag:bugsnag:9.9.9-test") - compile("com.bugsnag:bugsnag-spring:9.9.9-test") - compile project(":scenarios") + implementation("com.fasterxml.jackson.core:jackson-annotations:2.9.1") + implementation("com.fasterxml.jackson.core:jackson-databind:2.9.1") + implementation("com.bugsnag:bugsnag:9.9.9-test") + implementation("com.bugsnag:bugsnag-spring:9.9.9-test") + implementation project(":scenarios") } war { diff --git a/features/fixtures/mazerunnerspringboot/build.gradle b/features/fixtures/mazerunnerspringboot/build.gradle index 43bbd822..2a348654 100644 --- a/features/fixtures/mazerunnerspringboot/build.gradle +++ b/features/fixtures/mazerunnerspringboot/build.gradle @@ -27,18 +27,18 @@ repositories { } dependencies { - compile 'org.springframework.boot:spring-boot-starter' - compile 'org.springframework.boot:spring-boot-starter-web' - compile 'ch.qos.logback:logback-classic:1.2.3' + implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'ch.qos.logback:logback-classic:1.2.3' - compile 'com.fasterxml.jackson.core:jackson-annotations:2.9.1' - compile 'com.fasterxml.jackson.core:jackson-databind:2.9.1' - compile 'com.bugsnag:bugsnag:9.9.9-test' - compile 'com.bugsnag:bugsnag-spring:9.9.9-test' - compile project(":scenarios") + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.9.1' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.1' + implementation 'com.bugsnag:bugsnag:9.9.9-test' + implementation 'com.bugsnag:bugsnag-spring:9.9.9-test' + implementation project(":scenarios") // required for JDK 9 and above - compile('javax.xml.bind:jaxb-api:2.3.0') + implementation('javax.xml.bind:jaxb-api:2.3.0') - testCompile group: 'junit', name: 'junit', version: '4.13.2' + testImplementation group: 'junit', name: 'junit', version: '4.13.2' } \ No newline at end of file diff --git a/features/fixtures/scenarios/build.gradle b/features/fixtures/scenarios/build.gradle index 35fbc0cc..aee70512 100644 --- a/features/fixtures/scenarios/build.gradle +++ b/features/fixtures/scenarios/build.gradle @@ -12,7 +12,7 @@ repositories { } dependencies { - compile("ch.qos.logback:logback-classic:1.2.3") - compile("com.bugsnag:bugsnag:9.9.9-test") + implementation("ch.qos.logback:logback-classic:1.2.3") + implementation("com.bugsnag:bugsnag:9.9.9-test") } From fcd95ca954009f02e2f4846e726ea0efad01800c Mon Sep 17 00:00:00 2001 From: David Petran Date: Thu, 16 Mar 2023 16:32:29 +0000 Subject: [PATCH 07/35] always include examples in build --- settings.gradle | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/settings.gradle b/settings.gradle index 03c37e0c..3293fe57 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,12 +1,9 @@ include ':bugsnag', ':bugsnag-spring' -// Exclude examples from JDK7 build as they are written for JDK8 -if (JavaVersion.current().isJava8Compatible()) { - include ':examples:simple', - ':examples:servlet', - ':examples:spring', - ':examples:spring-web', - ':examples:logback' -} +include ':examples:simple', + ':examples:servlet', + ':examples:spring', + ':examples:spring-web', + ':examples:logback' From 0cee8010987ae831335a5c378c2ceae4ebeba2ed Mon Sep 17 00:00:00 2001 From: David Petran Date: Mon, 20 Mar 2023 09:06:30 +0000 Subject: [PATCH 08/35] remove unneeded throws declaration on overridden methods --- .../src/main/java/com/bugsnag/BugsnagSpringConfiguration.java | 2 +- bugsnag-spring/src/main/java/com/bugsnag/MvcConfiguration.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java index c10e50ae..43cdaa99 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java +++ b/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java @@ -65,7 +65,7 @@ ScheduledTaskBeanLocator scheduledTaskBeanLocator() { * messages as they effectively duplicate error reports for unhandled exceptions. */ @Override - public void afterPropertiesSet() throws java.lang.Exception { + public void afterPropertiesSet() { try { // Exclude Tomcat logger when processing HTTP requests via a servlet. // Regex specified to match the servlet variable parts of the logger name, e.g. diff --git a/bugsnag-spring/src/main/java/com/bugsnag/MvcConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/MvcConfiguration.java index 6d030120..33bc0c91 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/MvcConfiguration.java +++ b/bugsnag-spring/src/main/java/com/bugsnag/MvcConfiguration.java @@ -29,7 +29,7 @@ BugsnagMvcExceptionHandler bugsnagHandlerExceptionResolver() { * Add a callback to assign specified severities for some Spring exceptions. */ @Override - public void afterPropertiesSet() throws java.lang.Exception { + public void afterPropertiesSet() { bugsnag.addCallback(new ExceptionClassCallback()); } } From 6da52ba93e6fc9675b9b5403665fdf9ed1ff87ce Mon Sep 17 00:00:00 2001 From: David Petran Date: Mon, 20 Mar 2023 12:34:59 +0000 Subject: [PATCH 09/35] add jakarta notifier and config --- bugsnag-spring/build.gradle | 4 +- .../com/bugsnag/SpringBootConfiguration.java | 5 +- bugsnag/build.gradle | 6 +- .../main/java/com/bugsnag/Configuration.java | 11 +- .../callbacks/JakartaServletCallback.java | 89 +++++++++++ ...allback.java => JavaxServletCallback.java} | 6 +- .../BugsnagServletContainerInitializer.java | 14 ++ .../BugsnagServletRequestListener.java | 42 +++++ .../BugsnagServletContainerInitializer.java | 2 +- .../BugsnagServletRequestListener.java | 2 +- ...akarta.servlet.ServletContainerInitializer | 1 + .../javax.servlet.ServletContainerInitializer | 2 +- .../bugsnag/JakartaServletCallbackTest.java | 145 ++++++++++++++++++ ...est.java => JavaxServletCallbackTest.java} | 12 +- common.gradle | 3 +- 15 files changed, 322 insertions(+), 22 deletions(-) create mode 100644 bugsnag/src/main/java/com/bugsnag/callbacks/JakartaServletCallback.java rename bugsnag/src/main/java/com/bugsnag/callbacks/{ServletCallback.java => JavaxServletCallback.java} (94%) create mode 100644 bugsnag/src/main/java/com/bugsnag/servlet/jakarta/BugsnagServletContainerInitializer.java create mode 100644 bugsnag/src/main/java/com/bugsnag/servlet/jakarta/BugsnagServletRequestListener.java rename bugsnag/src/main/java/com/bugsnag/servlet/{ => javax}/BugsnagServletContainerInitializer.java (92%) rename bugsnag/src/main/java/com/bugsnag/servlet/{ => javax}/BugsnagServletRequestListener.java (97%) create mode 100644 bugsnag/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer create mode 100644 bugsnag/src/test/java/com/bugsnag/JakartaServletCallbackTest.java rename bugsnag/src/test/java/com/bugsnag/{ServletCallbackTest.java => JavaxServletCallbackTest.java} (93%) diff --git a/bugsnag-spring/build.gradle b/bugsnag-spring/build.gradle index 7bc443d6..b862fc87 100644 --- a/bugsnag-spring/build.gradle +++ b/bugsnag-spring/build.gradle @@ -18,14 +18,14 @@ dependencies { api project(':bugsnag') testImplementation project(':bugsnag').sourceSets.test.output - compileOnly "javax.servlet:javax.servlet-api:${servletApiVersion}" + 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:1.7.25" testImplementation "junit:junit:4.13.2" - testImplementation "javax.servlet:javax.servlet-api:${servletApiVersion}" + 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:5.0.0" diff --git a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java index e1a68105..c212ae7b 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java +++ b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java @@ -1,8 +1,9 @@ package com.bugsnag; import com.bugsnag.callbacks.Callback; -import com.bugsnag.servlet.BugsnagServletRequestListener; +import com.bugsnag.servlet.javax.BugsnagServletRequestListener; +import com.bugsnag.servlet.javax.BugsnagServletContainerInitializer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootVersion; import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; @@ -55,7 +56,7 @@ private void addSpringRuntimeVersion(Map device) { } /** - * The {@link com.bugsnag.servlet.BugsnagServletContainerInitializer} does not work for Spring Boot, need to + * The {@link 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. diff --git a/bugsnag/build.gradle b/bugsnag/build.gradle index 82d1a466..d91e4539 100644 --- a/bugsnag/build.gradle +++ b/bugsnag/build.gradle @@ -15,14 +15,16 @@ repositories { dependencies { api "com.fasterxml.jackson.core:jackson-databind:2.13.3" api "org.slf4j:slf4j-api:1.7.25" - compileOnly "javax.servlet:javax.servlet-api:${servletApiVersion}" + 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:4.13.2" testImplementation "org.slf4j:log4j-over-slf4j:1.7.25" - testImplementation "javax.servlet:javax.servlet-api:${servletApiVersion}" + testImplementation "javax.servlet:javax.servlet-api:${javaxServletApiVersion}" + testImplementation "jakarta.servlet:jakarta.servlet-api:${jakartaServletApiVersion}" testImplementation "org.mockito:mockito-core:5.0.0" testImplementation("ch.qos.logback:logback-classic:${logbackVersion}") { exclude group: "org.slf4j" diff --git a/bugsnag/src/main/java/com/bugsnag/Configuration.java b/bugsnag/src/main/java/com/bugsnag/Configuration.java index b9010e8f..2ebfbe52 100644 --- a/bugsnag/src/main/java/com/bugsnag/Configuration.java +++ b/bugsnag/src/main/java/com/bugsnag/Configuration.java @@ -3,7 +3,8 @@ import com.bugsnag.callbacks.AppCallback; import com.bugsnag.callbacks.Callback; import com.bugsnag.callbacks.DeviceCallback; -import com.bugsnag.callbacks.ServletCallback; +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; @@ -57,8 +58,12 @@ public class Configuration { addCallback(new DeviceCallback()); DeviceCallback.initializeCache(); - if (ServletCallback.isAvailable()) { - addCallback(new ServletCallback()); + if (JavaxServletCallback.isAvailable()) { + addCallback(new JavaxServletCallback()); + } + + if (JakartaServletCallback.isAvailable()) { + addCallback(new JakartaServletCallback()); } } diff --git a/bugsnag/src/main/java/com/bugsnag/callbacks/JakartaServletCallback.java b/bugsnag/src/main/java/com/bugsnag/callbacks/JakartaServletCallback.java new file mode 100644 index 00000000..3945f48e --- /dev/null +++ b/bugsnag/src/main/java/com/bugsnag/callbacks/JakartaServletCallback.java @@ -0,0 +1,89 @@ +package com.bugsnag.callbacks; + +import com.bugsnag.Report; +import com.bugsnag.servlet.jakarta.BugsnagServletRequestListener; + +import jakarta.servlet.http.HttpServletRequest; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +public class JakartaServletCallback implements Callback { + private static final String HEADER_X_FORWARDED_FOR = "X-FORWARDED-FOR"; + + /** + * @return true if the servlet request listener is available. + */ + public static boolean isAvailable() { + try { + Class.forName("jakarta.servlet.ServletRequestListener", false, + JakartaServletCallback.class.getClassLoader()); + return true; + } catch (ClassNotFoundException ex) { + return false; + } + } + + @Override + public void beforeNotify(Report report) { + // Check if we have any servlet request data available + HttpServletRequest request = BugsnagServletRequestListener.getServletRequest(); + if (request == null) { + return; + } + + // Add request information to metaData + report + .addToTab("request", "url", request.getRequestURL().toString()) + .addToTab("request", "method", request.getMethod()) + .addToTab("request", "params", + new HashMap(request.getParameterMap())) + .addToTab("request", "clientIp", getClientIp(request)) + .addToTab("request", "headers", getHeaderMap(request)); + + // Set default context + if (report.getContext() == null) { + report.setContext(request.getMethod() + " " + request.getRequestURI()); + } + } + + private String getClientIp(HttpServletRequest request) { + String remoteAddr = request.getRemoteAddr(); + String forwardedAddr = request.getHeader(HEADER_X_FORWARDED_FOR); + if (forwardedAddr != null) { + remoteAddr = forwardedAddr; + int idx = remoteAddr.indexOf(','); + if (idx > -1) { + remoteAddr = remoteAddr.substring(0, idx); + } + } + return remoteAddr; + } + + private Map getHeaderMap(HttpServletRequest request) { + Map headers = new HashMap(); + Enumeration headerNames = request.getHeaderNames(); + + while (headerNames != null && headerNames.hasMoreElements()) { + String key = headerNames.nextElement(); + Enumeration headerValues = request.getHeaders(key); + StringBuilder value = new StringBuilder(); + + if (headerValues != null && headerValues.hasMoreElements()) { + value.append(headerValues.nextElement()); + + // If there are multiple values for the header, do comma-separated concat + // as per RFC 2616: + // https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + while (headerValues.hasMoreElements()) { + value.append(",").append(headerValues.nextElement()); + } + } + + headers.put(key, value.toString()); + } + + return headers; + } +} diff --git a/bugsnag/src/main/java/com/bugsnag/callbacks/ServletCallback.java b/bugsnag/src/main/java/com/bugsnag/callbacks/JavaxServletCallback.java similarity index 94% rename from bugsnag/src/main/java/com/bugsnag/callbacks/ServletCallback.java rename to bugsnag/src/main/java/com/bugsnag/callbacks/JavaxServletCallback.java index 7d54e77c..84912fbb 100644 --- a/bugsnag/src/main/java/com/bugsnag/callbacks/ServletCallback.java +++ b/bugsnag/src/main/java/com/bugsnag/callbacks/JavaxServletCallback.java @@ -1,14 +1,14 @@ package com.bugsnag.callbacks; import com.bugsnag.Report; -import com.bugsnag.servlet.BugsnagServletRequestListener; +import com.bugsnag.servlet.javax.BugsnagServletRequestListener; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; -public class ServletCallback implements Callback { +public class JavaxServletCallback implements Callback { private static final String HEADER_X_FORWARDED_FOR = "X-FORWARDED-FOR"; /** @@ -17,7 +17,7 @@ public class ServletCallback implements Callback { public static boolean isAvailable() { try { Class.forName("javax.servlet.ServletRequestListener", false, - ServletCallback.class.getClassLoader()); + JavaxServletCallback.class.getClassLoader()); return true; } catch (ClassNotFoundException ex) { return false; diff --git a/bugsnag/src/main/java/com/bugsnag/servlet/jakarta/BugsnagServletContainerInitializer.java b/bugsnag/src/main/java/com/bugsnag/servlet/jakarta/BugsnagServletContainerInitializer.java new file mode 100644 index 00000000..65ad972b --- /dev/null +++ b/bugsnag/src/main/java/com/bugsnag/servlet/jakarta/BugsnagServletContainerInitializer.java @@ -0,0 +1,14 @@ +package com.bugsnag.servlet.jakarta; + +import jakarta.servlet.ServletContainerInitializer; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; + +import java.util.Set; + +public class BugsnagServletContainerInitializer implements ServletContainerInitializer { + @Override + public void onStartup(Set> cls, ServletContext context) throws ServletException { + context.addListener(BugsnagServletRequestListener.class); + } +} diff --git a/bugsnag/src/main/java/com/bugsnag/servlet/jakarta/BugsnagServletRequestListener.java b/bugsnag/src/main/java/com/bugsnag/servlet/jakarta/BugsnagServletRequestListener.java new file mode 100644 index 00000000..1867078f --- /dev/null +++ b/bugsnag/src/main/java/com/bugsnag/servlet/jakarta/BugsnagServletRequestListener.java @@ -0,0 +1,42 @@ +package com.bugsnag.servlet.jakarta; + +import com.bugsnag.Bugsnag; + +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletRequestEvent; +import jakarta.servlet.ServletRequestListener; +import jakarta.servlet.http.HttpServletRequest; + +public class BugsnagServletRequestListener implements ServletRequestListener { + + private static final ThreadLocal SERVLET_REQUEST = + new ThreadLocal(); + + public static HttpServletRequest getServletRequest() { + return SERVLET_REQUEST.get(); + } + + @Override + public void requestInitialized(ServletRequestEvent servletRequestEvent) { + trackServletSession(); + ServletRequest servletRequest = servletRequestEvent.getServletRequest(); + + if (servletRequest instanceof HttpServletRequest) { + SERVLET_REQUEST.set((HttpServletRequest) servletRequest); + } + } + + @Override + public void requestDestroyed(ServletRequestEvent servletRequestEvent) { + SERVLET_REQUEST.remove(); + Bugsnag.clearThreadMetaData(); + } + + private void trackServletSession() { + for (Bugsnag bugsnag : Bugsnag.uncaughtExceptionClients()) { + if (bugsnag.shouldAutoCaptureSessions()) { + bugsnag.startSession(); + } + } + } +} diff --git a/bugsnag/src/main/java/com/bugsnag/servlet/BugsnagServletContainerInitializer.java b/bugsnag/src/main/java/com/bugsnag/servlet/javax/BugsnagServletContainerInitializer.java similarity index 92% rename from bugsnag/src/main/java/com/bugsnag/servlet/BugsnagServletContainerInitializer.java rename to bugsnag/src/main/java/com/bugsnag/servlet/javax/BugsnagServletContainerInitializer.java index 187573b8..af177c35 100644 --- a/bugsnag/src/main/java/com/bugsnag/servlet/BugsnagServletContainerInitializer.java +++ b/bugsnag/src/main/java/com/bugsnag/servlet/javax/BugsnagServletContainerInitializer.java @@ -1,4 +1,4 @@ -package com.bugsnag.servlet; +package com.bugsnag.servlet.javax; import java.util.Set; import javax.servlet.ServletContainerInitializer; diff --git a/bugsnag/src/main/java/com/bugsnag/servlet/BugsnagServletRequestListener.java b/bugsnag/src/main/java/com/bugsnag/servlet/javax/BugsnagServletRequestListener.java similarity index 97% rename from bugsnag/src/main/java/com/bugsnag/servlet/BugsnagServletRequestListener.java rename to bugsnag/src/main/java/com/bugsnag/servlet/javax/BugsnagServletRequestListener.java index b95b2e86..a3e9e47a 100644 --- a/bugsnag/src/main/java/com/bugsnag/servlet/BugsnagServletRequestListener.java +++ b/bugsnag/src/main/java/com/bugsnag/servlet/javax/BugsnagServletRequestListener.java @@ -1,4 +1,4 @@ -package com.bugsnag.servlet; +package com.bugsnag.servlet.javax; import com.bugsnag.Bugsnag; diff --git a/bugsnag/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer b/bugsnag/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer new file mode 100644 index 00000000..9ce59b4c --- /dev/null +++ b/bugsnag/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer @@ -0,0 +1 @@ +com.bugsnag.servlet.jakarta.BugsnagServletContainerInitializer diff --git a/bugsnag/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/bugsnag/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer index 585bea80..a59bf666 100644 --- a/bugsnag/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer +++ b/bugsnag/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer @@ -1 +1 @@ -com.bugsnag.servlet.BugsnagServletContainerInitializer +com.bugsnag.servlet.javax.BugsnagServletContainerInitializer diff --git a/bugsnag/src/test/java/com/bugsnag/JakartaServletCallbackTest.java b/bugsnag/src/test/java/com/bugsnag/JakartaServletCallbackTest.java new file mode 100644 index 00000000..5bd78ec4 --- /dev/null +++ b/bugsnag/src/test/java/com/bugsnag/JakartaServletCallbackTest.java @@ -0,0 +1,145 @@ +package com.bugsnag; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.bugsnag.callbacks.JakartaServletCallback; +import com.bugsnag.servlet.jakarta.BugsnagServletRequestListener; + +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletRequestEvent; +import jakarta.servlet.http.HttpServletRequest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +public class JakartaServletCallbackTest { + + private Bugsnag bugsnag; + + /** + * Generate a new request instance which will be read by the servlet + * context and callback + */ + @Before + public void setUp() { + bugsnag = new Bugsnag("apikey", false); + bugsnag.setDelivery(null); + + HttpServletRequest request = mock(HttpServletRequest.class); + + Map params = new HashMap(); + params.put("account", new String[]{"Acme Co"}); + params.put("name", new String[]{"Bill"}); + when(request.getParameterMap()).thenReturn(params); + + when(request.getMethod()).thenReturn("PATCH"); + when(request.getRequestURL()).thenReturn(new StringBuffer("/foo/bar")); + when(request.getRequestURI()).thenReturn("/foo/bar"); + when(request.getRemoteAddr()).thenReturn("12.0.4.57"); + + when(request.getHeaderNames()).thenReturn( + stringsToEnumeration( + "Content-Type", + "Content-Length", + "X-Custom-Header", + "Authorization", + "Cookie")); + when(request.getHeaders("Content-Type")).thenReturn( + stringsToEnumeration("application/json")); + when(request.getHeaders("Content-Length")).thenReturn( + stringsToEnumeration("54")); + when(request.getHeaders("X-Custom-Header")).thenReturn( + stringsToEnumeration("some-data-1", "some-data-2")); + when(request.getHeaders("Authorization")).thenReturn( + stringsToEnumeration("Basic ABC123")); + when(request.getHeaders("Cookie")).thenReturn( + stringsToEnumeration("name1=val1; name2=val2")); + + ServletContext context = mock(ServletContext.class); + BugsnagServletRequestListener listener = new BugsnagServletRequestListener(); + listener.requestInitialized(new ServletRequestEvent(context, request)); + } + + /** + * Close test Bugsnag + */ + @After + public void closeBugsnag() { + bugsnag.close(); + } + + @SuppressWarnings("unchecked") + @Test + public void testRequestMetadataAdded() { + Report report = generateReport(new java.lang.Exception("Spline reticulation failed")); + JakartaServletCallback callback = new JakartaServletCallback(); + callback.beforeNotify(report); + + Map metadata = report.getMetaData(); + assertTrue(metadata.containsKey("request")); + + Map request = (Map) metadata.get("request"); + assertEquals("/foo/bar", request.get("url")); + assertEquals("PATCH", request.get("method")); + assertEquals("12.0.4.57", request.get("clientIp")); + + assertTrue(request.containsKey("headers")); + Map headers = (Map) request.get("headers"); + assertEquals("application/json", headers.get("Content-Type")); + assertEquals("54", headers.get("Content-Length")); + assertEquals("some-data-1,some-data-2", headers.get("X-Custom-Header")); + + // Make sure that actual Authorization header value is not in the report + assertEquals("[FILTERED]", headers.get("Authorization")); + + // Make sure that actual cookies are not in the report + assertEquals("[FILTERED]", headers.get("Cookie")); + + assertTrue(request.containsKey("params")); + Map params = (Map) request.get("params"); + assertTrue(params.containsKey("account")); + String[] account = params.get("account"); + assertEquals("Acme Co", account[0]); + + assertTrue(params.containsKey("name")); + String[] name = params.get("name"); + assertEquals("Bill", name[0]); + } + + @Test + public void testRequestContextSet() { + Report report = generateReport(new java.lang.Exception("Spline reticulation failed")); + JakartaServletCallback callback = new JakartaServletCallback(); + callback.beforeNotify(report); + + assertEquals("PATCH /foo/bar", report.getContext()); + } + + @Test + public void testExistingContextNotOverridden() { + Report report = generateReport(new java.lang.Exception("Spline reticulation failed")); + report.setContext("Honey nut corn flakes"); + JakartaServletCallback callback = new JakartaServletCallback(); + callback.beforeNotify(report); + + assertEquals("Honey nut corn flakes", report.getContext()); + } + + private Report generateReport(java.lang.Exception exception) { + return bugsnag.buildReport(exception); + } + + private Enumeration stringsToEnumeration(String... strings) { + return Collections.enumeration(Arrays.asList(strings)); + } +} diff --git a/bugsnag/src/test/java/com/bugsnag/ServletCallbackTest.java b/bugsnag/src/test/java/com/bugsnag/JavaxServletCallbackTest.java similarity index 93% rename from bugsnag/src/test/java/com/bugsnag/ServletCallbackTest.java rename to bugsnag/src/test/java/com/bugsnag/JavaxServletCallbackTest.java index 6b1fabb0..a4ffa9f7 100644 --- a/bugsnag/src/test/java/com/bugsnag/ServletCallbackTest.java +++ b/bugsnag/src/test/java/com/bugsnag/JavaxServletCallbackTest.java @@ -5,9 +5,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.bugsnag.callbacks.ServletCallback; +import com.bugsnag.callbacks.JavaxServletCallback; -import com.bugsnag.servlet.BugsnagServletRequestListener; +import com.bugsnag.servlet.javax.BugsnagServletRequestListener; import org.junit.After; import org.junit.Before; @@ -22,7 +22,7 @@ import javax.servlet.ServletRequestEvent; import javax.servlet.http.HttpServletRequest; -public class ServletCallbackTest { +public class JavaxServletCallbackTest { private Bugsnag bugsnag; @@ -82,7 +82,7 @@ public void closeBugsnag() { @Test public void testRequestMetadataAdded() { Report report = generateReport(new java.lang.Exception("Spline reticulation failed")); - ServletCallback callback = new ServletCallback(); + JavaxServletCallback callback = new JavaxServletCallback(); callback.beforeNotify(report); Map metadata = report.getMetaData(); @@ -119,7 +119,7 @@ public void testRequestMetadataAdded() { @Test public void testRequestContextSet() { Report report = generateReport(new java.lang.Exception("Spline reticulation failed")); - ServletCallback callback = new ServletCallback(); + JavaxServletCallback callback = new JavaxServletCallback(); callback.beforeNotify(report); assertEquals("PATCH /foo/bar", report.getContext()); @@ -129,7 +129,7 @@ public void testRequestContextSet() { public void testExistingContextNotOverridden() { Report report = generateReport(new java.lang.Exception("Spline reticulation failed")); report.setContext("Honey nut corn flakes"); - ServletCallback callback = new ServletCallback(); + JavaxServletCallback callback = new JavaxServletCallback(); callback.beforeNotify(report); assertEquals("Honey nut corn flakes", report.getContext()); diff --git a/common.gradle b/common.gradle index bfacccf4..00223406 100644 --- a/common.gradle +++ b/common.gradle @@ -1,5 +1,6 @@ ext { - servletApiVersion = "3.1.0" + javaxServletApiVersion = "3.1.0" + jakartaServletApiVersion = "5.0.0" logbackVersion = "1.2.3" } From 5ee88615f4dd9d11ee71ad8d43bd9b8bfdfd53bd Mon Sep 17 00:00:00 2001 From: David Petran Date: Tue, 21 Mar 2023 16:12:52 +0000 Subject: [PATCH 10/35] add jakarta servlet example app --- examples/servlet-jakarta/README.md | 21 +++++++++ examples/servlet-jakarta/build.gradle | 29 ++++++++++++ .../bugsnag/example/servlet/ErrorFilter.java | 32 +++++++++++++ .../example/servlet/ExampleServlet.java | 46 +++++++++++++++++++ .../src/main/webapp/WEB-INF/.gitkeep | 0 examples/{servlet => servlet-javax}/README.md | 2 +- .../{servlet => servlet-javax}/build.gradle | 0 .../bugsnag/example/servlet/ErrorHandler.java | 0 .../example/servlet/ExampleServlet.java | 0 .../src/main/webapp/WEB-INF/web.xml | 0 settings.gradle | 3 +- 11 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 examples/servlet-jakarta/README.md create mode 100644 examples/servlet-jakarta/build.gradle create mode 100644 examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ErrorFilter.java create mode 100644 examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ExampleServlet.java create mode 100644 examples/servlet-jakarta/src/main/webapp/WEB-INF/.gitkeep rename examples/{servlet => servlet-javax}/README.md (93%) rename examples/{servlet => servlet-javax}/build.gradle (100%) rename examples/{servlet => servlet-javax}/src/main/java/com/bugsnag/example/servlet/ErrorHandler.java (100%) rename examples/{servlet => servlet-javax}/src/main/java/com/bugsnag/example/servlet/ExampleServlet.java (100%) rename examples/{servlet => servlet-javax}/src/main/webapp/WEB-INF/web.xml (100%) diff --git a/examples/servlet-jakarta/README.md b/examples/servlet-jakarta/README.md new file mode 100644 index 00000000..e8bc33be --- /dev/null +++ b/examples/servlet-jakarta/README.md @@ -0,0 +1,21 @@ +# Bugsnag Jakarta Servlet Example + +Demonstrates how to use Bugsnag in a Servlet-based Java application. + +1. Open `ExampleServlet` and alter the value of `bugsnag = new Bugsnag("YOUR-API-KEY");` to match your API key + +2. Build the app + + ```shell + gradle clean assemble + ``` + +3. Start the web server + + ```shell + gradle appRun + ``` + +4. Cause a crash by visiting [http://localhost:8080/servlet](http://localhost:8080/servlet) + +5. View the captured errors in [your dashboard](https://app.bugsnag.com) \ No newline at end of file diff --git a/examples/servlet-jakarta/build.gradle b/examples/servlet-jakarta/build.gradle new file mode 100644 index 00000000..0ea1f37f --- /dev/null +++ b/examples/servlet-jakarta/build.gradle @@ -0,0 +1,29 @@ +apply plugin: 'java' +apply plugin: 'war' +apply plugin: 'org.gretty' + +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath 'org.gretty:gretty:4.0.3' + } +} + +repositories { + mavenCentral() +} + +dependencies { + runtimeOnly 'org.slf4j:slf4j-simple:1.7.25' + implementation "jakarta.servlet:jakarta.servlet-api:5.0.0" + implementation project(':bugsnag') +} + +gretty { + httpPort = 8083 + contextPath = '/' + jvmArgs = ['-Dorg.slf4j.simpleLogger.defaultLogLevel=DEBUG'] +} \ No newline at end of file diff --git a/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ErrorFilter.java b/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ErrorFilter.java new file mode 100644 index 00000000..40336d8f --- /dev/null +++ b/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ErrorFilter.java @@ -0,0 +1,32 @@ +package com.bugsnag.example.servlet; + +import com.bugsnag.Bugsnag; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.annotation.WebFilter; +import jakarta.servlet.http.HttpFilter; + +import java.io.IOException; + +@WebFilter(urlPatterns = {"/*"}, asyncSupported=true) +public class ErrorFilter extends HttpFilter { + + private Bugsnag bugsnag; + + public ErrorFilter() { + bugsnag = new Bugsnag("YOUR-API-KEY"); + bugsnag.setProjectPackages("com.bugsnag.example"); + } + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { + try { + super.doFilter(req, res, chain); + } catch (ServletException servletException) { + bugsnag.notify(servletException); + throw servletException; + } + } +} \ No newline at end of file diff --git a/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ExampleServlet.java b/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ExampleServlet.java new file mode 100644 index 00000000..2ee201ed --- /dev/null +++ b/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ExampleServlet.java @@ -0,0 +1,46 @@ +package com.bugsnag.example.servlet; + +import com.bugsnag.Bugsnag; +import com.bugsnag.Severity; + +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@WebServlet(urlPatterns = {"/"}, name = "ExampleServlet", displayName = "ExampleServlet", asyncSupported = true) +public class ExampleServlet extends HttpServlet { + + private static final long serialVersionUID = 1432171052111530587L; + + private Bugsnag bugsnag; + + /** + * Simple servlet example + */ + public ExampleServlet() { + bugsnag = new Bugsnag("YOUR-API-KEY"); + bugsnag.setProjectPackages("com.bugsnag.example"); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException { + // Send a handled exception to Bugsnag + try { + throw new RuntimeException("Handled exception - default severity"); + } catch (RuntimeException e) { + bugsnag.notify(e); + } + + // Send a handled exception to Bugsnag with info severity + try { + throw new RuntimeException("Handled exception - INFO severity"); + } catch (RuntimeException ex) { + bugsnag.notify(ex, Severity.INFO); + } + + // Throw an exception - not automatically reported so must be handled by the error handler + throw new ServletException("Servlet exception"); + } +} diff --git a/examples/servlet-jakarta/src/main/webapp/WEB-INF/.gitkeep b/examples/servlet-jakarta/src/main/webapp/WEB-INF/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/examples/servlet/README.md b/examples/servlet-javax/README.md similarity index 93% rename from examples/servlet/README.md rename to examples/servlet-javax/README.md index 0bb199b7..b81c6e81 100644 --- a/examples/servlet/README.md +++ b/examples/servlet-javax/README.md @@ -1,4 +1,4 @@ -# Bugsnag Servlet Example +# Bugsnag Javax Servlet Example Demonstrates how to use Bugsnag in a Servlet-based Java application. diff --git a/examples/servlet/build.gradle b/examples/servlet-javax/build.gradle similarity index 100% rename from examples/servlet/build.gradle rename to examples/servlet-javax/build.gradle diff --git a/examples/servlet/src/main/java/com/bugsnag/example/servlet/ErrorHandler.java b/examples/servlet-javax/src/main/java/com/bugsnag/example/servlet/ErrorHandler.java similarity index 100% rename from examples/servlet/src/main/java/com/bugsnag/example/servlet/ErrorHandler.java rename to examples/servlet-javax/src/main/java/com/bugsnag/example/servlet/ErrorHandler.java diff --git a/examples/servlet/src/main/java/com/bugsnag/example/servlet/ExampleServlet.java b/examples/servlet-javax/src/main/java/com/bugsnag/example/servlet/ExampleServlet.java similarity index 100% rename from examples/servlet/src/main/java/com/bugsnag/example/servlet/ExampleServlet.java rename to examples/servlet-javax/src/main/java/com/bugsnag/example/servlet/ExampleServlet.java diff --git a/examples/servlet/src/main/webapp/WEB-INF/web.xml b/examples/servlet-javax/src/main/webapp/WEB-INF/web.xml similarity index 100% rename from examples/servlet/src/main/webapp/WEB-INF/web.xml rename to examples/servlet-javax/src/main/webapp/WEB-INF/web.xml diff --git a/settings.gradle b/settings.gradle index 3293fe57..b8133c81 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,7 +2,8 @@ include ':bugsnag', ':bugsnag-spring' include ':examples:simple', - ':examples:servlet', + ':examples:servlet-jakarta', + ':examples:servlet-javax', ':examples:spring', ':examples:spring-web', ':examples:logback' From 4853bd262e77304f44f9060559980525a26916cf Mon Sep 17 00:00:00 2001 From: David Petran Date: Tue, 21 Mar 2023 16:23:57 +0000 Subject: [PATCH 11/35] checkstyle fix --- .../src/main/java/com/bugsnag/SpringBootConfiguration.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java index c212ae7b..275a8350 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java +++ b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java @@ -3,7 +3,6 @@ import com.bugsnag.callbacks.Callback; import com.bugsnag.servlet.javax.BugsnagServletRequestListener; -import com.bugsnag.servlet.javax.BugsnagServletContainerInitializer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootVersion; import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; @@ -56,7 +55,7 @@ private void addSpringRuntimeVersion(Map device) { } /** - * The {@link BugsnagServletContainerInitializer} does not work for Spring Boot, need to + * 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. From a88f6cdab68283325d744cac24d41041fd303b97 Mon Sep 17 00:00:00 2001 From: David Petran Date: Tue, 21 Mar 2023 17:29:05 +0000 Subject: [PATCH 12/35] don't build jakarta servlet example unless java version > 11 --- settings.gradle | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index b8133c81..596924ce 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,9 +2,12 @@ include ':bugsnag', ':bugsnag-spring' include ':examples:simple', - ':examples:servlet-jakarta', ':examples:servlet-javax', ':examples:spring', ':examples:spring-web', ':examples:logback' +// jakarta servlet example requires java 11 compatibility for gretty plugin +if (JavaVersion.current().isJava11Compatible()) { + include ':examples:servlet-jakarta' +} \ No newline at end of file From 433df9991a9276083405f3aa44df72cc0d3f0b01 Mon Sep 17 00:00:00 2001 From: David Petran Date: Thu, 23 Mar 2023 12:35:42 +0000 Subject: [PATCH 13/35] revert jakarta example --- .../bugsnag/example/servlet/ErrorFilter.java | 32 ------------------- .../bugsnag/example/servlet/ErrorHandler.java | 31 ++++++++++++++++++ .../example/servlet/ExampleServlet.java | 2 -- .../src/main/webapp/WEB-INF/.gitkeep | 0 .../src/main/webapp/WEB-INF/web.xml | 30 +++++++++++++++++ 5 files changed, 61 insertions(+), 34 deletions(-) delete mode 100644 examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ErrorFilter.java create mode 100644 examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ErrorHandler.java delete mode 100644 examples/servlet-jakarta/src/main/webapp/WEB-INF/.gitkeep create mode 100644 examples/servlet-jakarta/src/main/webapp/WEB-INF/web.xml diff --git a/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ErrorFilter.java b/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ErrorFilter.java deleted file mode 100644 index 40336d8f..00000000 --- a/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ErrorFilter.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.bugsnag.example.servlet; - -import com.bugsnag.Bugsnag; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.annotation.WebFilter; -import jakarta.servlet.http.HttpFilter; - -import java.io.IOException; - -@WebFilter(urlPatterns = {"/*"}, asyncSupported=true) -public class ErrorFilter extends HttpFilter { - - private Bugsnag bugsnag; - - public ErrorFilter() { - bugsnag = new Bugsnag("YOUR-API-KEY"); - bugsnag.setProjectPackages("com.bugsnag.example"); - } - - @Override - public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { - try { - super.doFilter(req, res, chain); - } catch (ServletException servletException) { - bugsnag.notify(servletException); - throw servletException; - } - } -} \ No newline at end of file diff --git a/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ErrorHandler.java b/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ErrorHandler.java new file mode 100644 index 00000000..3d93adf5 --- /dev/null +++ b/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ErrorHandler.java @@ -0,0 +1,31 @@ +package com.bugsnag.example.servlet; + +import com.bugsnag.Bugsnag; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class ErrorHandler extends HttpServlet { + + private static final long serialVersionUID = 4926619146717832212L; + + private Bugsnag bugsnag; + + /** + * Error handler to report the error to Bugsnag + */ + public ErrorHandler() { + bugsnag = new Bugsnag("YOUR-API-KEY"); + bugsnag.setProjectPackages("com.bugsnag.example"); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + // Notify Bugsnag of the exception + Throwable throwable = (Throwable) req.getAttribute("jakarta.servlet.error.exception"); + bugsnag.notify(throwable); + } +} diff --git a/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ExampleServlet.java b/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ExampleServlet.java index 2ee201ed..12a0ee6f 100644 --- a/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ExampleServlet.java +++ b/examples/servlet-jakarta/src/main/java/com/bugsnag/example/servlet/ExampleServlet.java @@ -4,12 +4,10 @@ import com.bugsnag.Severity; import jakarta.servlet.ServletException; -import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -@WebServlet(urlPatterns = {"/"}, name = "ExampleServlet", displayName = "ExampleServlet", asyncSupported = true) public class ExampleServlet extends HttpServlet { private static final long serialVersionUID = 1432171052111530587L; diff --git a/examples/servlet-jakarta/src/main/webapp/WEB-INF/.gitkeep b/examples/servlet-jakarta/src/main/webapp/WEB-INF/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/servlet-jakarta/src/main/webapp/WEB-INF/web.xml b/examples/servlet-jakarta/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..ffbc2a38 --- /dev/null +++ b/examples/servlet-jakarta/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,30 @@ + + + + ExampleServlet + ExampleServlet + com.bugsnag.example.servlet.ExampleServlet + + + ErrorHandler + com.bugsnag.example.servlet.ErrorHandler + + + + ExampleServlet + / + + + ErrorHandler + /ErrorHandler + + + + + jakarta.servlet.ServletException + /ErrorHandler + + From ba901eec203d113d9e41c4faf21e1d71dcfa8478 Mon Sep 17 00:00:00 2001 From: David Petran Date: Fri, 24 Mar 2023 16:48:36 +0000 Subject: [PATCH 14/35] mazerunner apps for springboot3/spring6 and ci steps --- .buildkite/pipeline.yml | 27 ++++- docker-compose.yml | 2 +- dockerfiles/Dockerfile.java17-mazerunner | 20 ++++ ...mazerunner => Dockerfile.java8-mazerunner} | 0 .../mazerunner/src/main/resources/logback.xml | 18 ++++ .../mazerunnerplainspring6/build.gradle | 28 ++++++ .../AppConfig.java | 22 +++++ .../AppInitializer.java | 22 +++++ .../AsyncMethodService.java | 29 ++++++ .../BugsnagAsyncConfig.java | 19 ++++ .../BugsnagConfig.java | 34 +++++++ .../ScheduledTaskService.java | 29 ++++++ .../TestRestController.java | 99 +++++++++++++++++++ .../mazerunnerspringboot3/build.gradle | 41 ++++++++ .../scenarios/AsyncMethodScenario.java | 34 +++++++ .../scenarios/AsyncNotifyScenario.java | 34 +++++++ .../scenarios/AutoSessionScenario.java | 38 +++++++ .../scenarios/RestControllerScenario.java | 29 ++++++ .../ScheduledTaskExecutorScenario.java | 36 +++++++ .../scenarios/ScheduledTaskScenario.java | 27 +++++ .../mazerunnerspringboot/Application.java | 18 ++++ .../AsyncMethodService.java | 39 ++++++++ .../BugsnagAsyncConfig.java | 21 ++++ .../bugsnag/mazerunnerspringboot/Config.java | 28 ++++++ .../ScheduledTaskConfig.java | 44 +++++++++ .../ScheduledTaskExecutorService.java | 41 ++++++++ .../ScheduledTaskService.java | 33 +++++++ .../mazerunnerspringboot/TestCaseRunner.java | 66 +++++++++++++ .../TestRestController.java | 71 +++++++++++++ .../src/main/resources/application.properties | 1 + features/fixtures/settings.gradle | 5 + features/scripts/build-plain-spring-app.sh | 9 +- features/scripts/run-java-spring-boot-app.sh | 6 +- features/steps/build_steps.rb | 24 +++++ 34 files changed, 985 insertions(+), 9 deletions(-) create mode 100644 dockerfiles/Dockerfile.java17-mazerunner rename dockerfiles/{Dockerfile.java-mazerunner => Dockerfile.java8-mazerunner} (100%) create mode 100644 features/fixtures/mazerunner/src/main/resources/logback.xml create mode 100644 features/fixtures/mazerunnerplainspring6/build.gradle create mode 100644 features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/AppConfig.java create mode 100644 features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/AppInitializer.java create mode 100644 features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/AsyncMethodService.java create mode 100644 features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/BugsnagAsyncConfig.java create mode 100644 features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/BugsnagConfig.java create mode 100644 features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/ScheduledTaskService.java create mode 100644 features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/TestRestController.java create mode 100644 features/fixtures/mazerunnerspringboot3/build.gradle create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/AsyncMethodScenario.java create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/AsyncNotifyScenario.java create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/AutoSessionScenario.java create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/RestControllerScenario.java create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/ScheduledTaskExecutorScenario.java create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/ScheduledTaskScenario.java create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/Application.java create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/AsyncMethodService.java create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/BugsnagAsyncConfig.java create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/Config.java create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/ScheduledTaskConfig.java create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/ScheduledTaskExecutorService.java create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/ScheduledTaskService.java create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/TestCaseRunner.java create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/TestRestController.java create mode 100644 features/fixtures/mazerunnerspringboot3/src/main/resources/application.properties diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 91cdb616..5b906dd9 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -21,19 +21,36 @@ steps: run: java-common command: './gradlew check test' - - label: ':docker: Mazerunner tests batch 1' + - label: ':docker: Mazerunner java8 tests batch 1' key: 'java-mazerunner-tests-1' timeout_in_minutes: 30 plugins: - docker-compose#v3.7.0: - run: java-mazerunner + run: java8-mazerunner command: 'bundle exec maze-runner --exclude=features/[^a-m].*.feature' - - label: ':docker: Mazerunner tests batch 2' + - label: ':docker: Mazerunner java8 tests batch 2' key: 'java-mazerunner-tests-2' timeout_in_minutes: 30 plugins: - docker-compose#v3.7.0: - pull: java-mazerunner - run: java-mazerunner + pull: java8-mazerunner + run: java8-mazerunner + command: 'bundle exec maze-runner --exclude=features/[^n-z].*.feature' + + - label: ':docker: Mazerunner java17 tests batch 1' + key: 'java-mazerunner-tests-3' + timeout_in_minutes: 30 + plugins: + - docker-compose#v3.7.0: + run: java17-mazerunner + command: 'bundle exec maze-runner --exclude=features/[^a-m].*.feature' + + - label: ':docker: Mazerunner java17 tests batch 2' + key: 'java-mazerunner-tests-4' + timeout_in_minutes: 30 + plugins: + - docker-compose#v3.7.0: + pull: java17-mazerunner + run: java17-mazerunner command: 'bundle exec maze-runner --exclude=features/[^n-z].*.feature' diff --git a/docker-compose.yml b/docker-compose.yml index d6a188bf..dd322317 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,4 +11,4 @@ services: java-mazerunner: build: context: . - dockerfile: dockerfiles/Dockerfile.java-mazerunner + dockerfile: dockerfiles/Dockerfile.java8-mazerunner diff --git a/dockerfiles/Dockerfile.java17-mazerunner b/dockerfiles/Dockerfile.java17-mazerunner new file mode 100644 index 00000000..89a134d6 --- /dev/null +++ b/dockerfiles/Dockerfile.java17-mazerunner @@ -0,0 +1,20 @@ +FROM tomcat:9.0.56-jdk17-temurin +WORKDIR /app + +RUN apt-get update && DEBIAN_FRONTEND=noninteractive \ + apt-get install -y -q docker-compose bundler libcurl4-openssl-dev + +# Force download of gradle zip early to avoid repeating +# if Docker cache is invalidated by branch changes. +COPY gradlew gradle.properties /app/ +COPY gradle/ /app/gradle/ +ENV GRADLE_OPTS="-Dorg.gradle.daemon=false" +COPY settings.gradle /app/ +RUN ./gradlew + +# Copy repo into docker +COPY . /app + +# Setup mazerunner +RUN gem install bundler:1.16.5 +RUN bundle install diff --git a/dockerfiles/Dockerfile.java-mazerunner b/dockerfiles/Dockerfile.java8-mazerunner similarity index 100% rename from dockerfiles/Dockerfile.java-mazerunner rename to dockerfiles/Dockerfile.java8-mazerunner diff --git a/features/fixtures/mazerunner/src/main/resources/logback.xml b/features/fixtures/mazerunner/src/main/resources/logback.xml new file mode 100644 index 00000000..60ade3a1 --- /dev/null +++ b/features/fixtures/mazerunner/src/main/resources/logback.xml @@ -0,0 +1,18 @@ + + + + + + a35a2a72bd230ac0aa0f52715bbdc6aa + production + 1.0.0 + + testAppType + + http://localhost:9339/notify + + + + + + diff --git a/features/fixtures/mazerunnerplainspring6/build.gradle b/features/fixtures/mazerunnerplainspring6/build.gradle new file mode 100644 index 00000000..e25fc418 --- /dev/null +++ b/features/fixtures/mazerunnerplainspring6/build.gradle @@ -0,0 +1,28 @@ +plugins { + id "war" +} + +group 'com.bugsnag.mazerunnerplainspring6' + +repositories { + mavenCentral() + flatDir { + dirs '../libs' + } +} + +dependencies { + implementation("org.springframework:spring-webmvc:6.0.0") + implementation("jakarta.servlet:jakarta.servlet-api:5.0.0") + implementation("ch.qos.logback:logback-classic:1.2.3") + + implementation("com.fasterxml.jackson.core:jackson-annotations:2.9.1") + implementation("com.fasterxml.jackson.core:jackson-databind:2.9.1") + implementation("com.bugsnag:bugsnag:9.9.9-test") + implementation("com.bugsnag:bugsnag-spring:9.9.9-test") + implementation project(":scenarios") +} + +war { + archiveName = 'mazerunnerplainspring6.war' +} \ No newline at end of file diff --git a/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/AppConfig.java b/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/AppConfig.java new file mode 100644 index 00000000..d6389828 --- /dev/null +++ b/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/AppConfig.java @@ -0,0 +1,22 @@ +package com.bugsnag.mazerunnerplainspring; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +@Configuration +@EnableWebMvc +@EnableScheduling +@EnableAsync +@ComponentScan(basePackages = "com.bugsnag.mazerunnerplainspring") +public class AppConfig { + @Bean + public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } +} \ No newline at end of file diff --git a/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/AppInitializer.java b/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/AppInitializer.java new file mode 100644 index 00000000..2fd1cf0f --- /dev/null +++ b/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/AppInitializer.java @@ -0,0 +1,22 @@ +package com.bugsnag.mazerunnerplainspring; + +import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; + +public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { + + @Override + protected Class[] getRootConfigClasses() { + return new Class[] { AppConfig.class }; + } + + @Override + protected Class[] getServletConfigClasses() { + return null; + } + + @Override + protected String[] getServletMappings() { + return new String[] { "/" }; + } + +} \ No newline at end of file diff --git a/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/AsyncMethodService.java b/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/AsyncMethodService.java new file mode 100644 index 00000000..f1d52269 --- /dev/null +++ b/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/AsyncMethodService.java @@ -0,0 +1,29 @@ +package com.bugsnag.mazerunnerplainspring; + +import com.bugsnag.Bugsnag; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.util.concurrent.Future; + +@Service +public class AsyncMethodService { + + @Async + public void doSomethingAsync() { + + // Add some thread meta data + Bugsnag.addThreadMetaData("thread", "key1", "should be cleared from meta data"); + Bugsnag.clearThreadMetaData(); + Bugsnag.addThreadMetaData("thread", "key2", "should be included in meta data"); + + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // ignore + } + + throw new RuntimeException("Unhandled exception from Async method"); + } +} diff --git a/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/BugsnagAsyncConfig.java b/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/BugsnagAsyncConfig.java new file mode 100644 index 00000000..d1b0e59e --- /dev/null +++ b/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/BugsnagAsyncConfig.java @@ -0,0 +1,19 @@ +package com.bugsnag.mazerunnerplainspring; + +import com.bugsnag.Bugsnag; +import com.bugsnag.BugsnagAsyncExceptionHandler; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurerSupport; + +@Configuration +public class BugsnagAsyncConfig extends AsyncConfigurerSupport { + @Autowired + private Bugsnag bugsnag; + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return new BugsnagAsyncExceptionHandler(bugsnag); + } +} diff --git a/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/BugsnagConfig.java b/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/BugsnagConfig.java new file mode 100644 index 00000000..cf5d52ca --- /dev/null +++ b/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/BugsnagConfig.java @@ -0,0 +1,34 @@ +package com.bugsnag.mazerunnerplainspring; + +import com.bugsnag.Bugsnag; +import com.bugsnag.BugsnagSpringConfiguration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.util.StringUtils; + +@Configuration +@Import(BugsnagSpringConfiguration.class) +public class BugsnagConfig { + + @Value("${BUGSNAG_API_KEY}") + private String bugsnagApiKey; + + @Value("${MAZERUNNER_BASE_URL}") + private String bugsnagEndpoint; + + @Value("${AUTO_CAPTURE_SESSIONS:false}") + private boolean autoCaptureSessions; + + @Bean + public Bugsnag bugsnag() { + Bugsnag bugsnag = new Bugsnag(bugsnagApiKey); + bugsnag.setEndpoints(bugsnagEndpoint + "notify", bugsnagEndpoint + "sessions"); + bugsnag.setAutoCaptureSessions(autoCaptureSessions); + bugsnag.setReleaseStage("production"); + bugsnag.setAppVersion("1.0.0"); + return bugsnag; + } +} diff --git a/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/ScheduledTaskService.java b/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/ScheduledTaskService.java new file mode 100644 index 00000000..3574ff08 --- /dev/null +++ b/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/ScheduledTaskService.java @@ -0,0 +1,29 @@ +package com.bugsnag.mazerunnerplainspring; + +import com.bugsnag.Bugsnag; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Service +public class ScheduledTaskService { + + @Value("${RUN_SCHEDULED_TASK:false}") + private boolean throwException; + + private volatile boolean exceptionSent = false; + + @Scheduled(fixedDelay = 3000) + public void doSomething() { + if (throwException && !exceptionSent) { + + // Add some thread meta data + Bugsnag.addThreadMetaData("thread", "key1", "should be cleared from meta data"); + Bugsnag.clearThreadMetaData(); + Bugsnag.addThreadMetaData("thread", "key2", "should be included in meta data"); + + exceptionSent = true; + throw new RuntimeException("Unhandled exception from ScheduledTaskService"); + } + } +} diff --git a/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/TestRestController.java b/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/TestRestController.java new file mode 100644 index 00000000..59496a2f --- /dev/null +++ b/features/fixtures/mazerunnerplainspring6/src/main/java/com.bugsnag.mazerunnerplainspring/TestRestController.java @@ -0,0 +1,99 @@ +package com.bugsnag.mazerunnerplainspring; + +import com.bugsnag.Bugsnag; +import com.bugsnag.mazerunner.scenarios.Scenario; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; + +@RestController +public class TestRestController { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestRestController.class); + + @Autowired + private Bugsnag bugsnag; + + @Autowired + private AsyncMethodService asyncMethodService; + + @RequestMapping("/") + public String ping() { + return "Plain Spring Fixture app ready for connections"; + } + + @RequestMapping("/send-unhandled-exception") + public void sendUnhandledException() { + throw new RuntimeException("Unhandled exception from TestRestController"); + } + + @RequestMapping("/add-session") + public void addSession() { + // A session should be automatically recorded by Bugsnag if automatic sessions are enabled + LOGGER.info("Starting a new session"); + + // Flush sessions now, otherwise need to wait for sessions to be automatically flushed + flushAllSessions(); + LOGGER.info("Flushed all sessions"); + } + + @RequestMapping("/run-async-task") + public void runAsyncTask() { + try { + asyncMethodService.doSomethingAsync(); + } catch (Exception ex) { + // This should not happen + LOGGER.info("Saw exception from async call"); + } + } + + @RequestMapping("/run-scenario/{scenario}") + public void runScenario(@PathVariable String scenario) { + try { + Class clz = Class.forName("com.bugsnag.mazerunner.scenarios." + scenario); + Constructor constructor = clz.getConstructors()[0]; + ((Scenario) constructor.newInstance(bugsnag)).run(); + } catch (Exception ex) { + LOGGER.error("Error getting scenario", ex); + } + } + + /** + * Flushes sessions from the Bugsnag object + */ + private void flushAllSessions() { + try { + Field field = bugsnag.getClass().getDeclaredField("sessionTracker"); + field.setAccessible(true); + Object sessionTracker = field.get(bugsnag); + + field = sessionTracker.getClass().getDeclaredField("enqueuedSessionCounts"); + field.setAccessible(true); + Collection sessionCounts = (Collection) field.get(sessionTracker); + + // Flush the sessions + Method method = sessionTracker.getClass().getDeclaredMethod("flushSessions", Date.class); + method.setAccessible(true); + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.MINUTE, 2); + method.invoke(sessionTracker, calendar.getTime()); + + // Wait until sessions are flushed + while (sessionCounts.size() > 0) { + Thread.sleep(1000); + } + } catch (Exception ex) { + LOGGER.error("failed to flush sessions", ex); + } + } +} diff --git a/features/fixtures/mazerunnerspringboot3/build.gradle b/features/fixtures/mazerunnerspringboot3/build.gradle new file mode 100644 index 00000000..7e8b4c36 --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/build.gradle @@ -0,0 +1,41 @@ +buildscript { + ext { + springBootVersion = '3.0.0' + } + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + } +} + +apply plugin: 'java' +apply plugin: 'org.springframework.boot' +apply plugin: 'io.spring.dependency-management' + +group 'com.bugsnag.mazerunnerspringboot3' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() + flatDir { + dirs '../libs' + } +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-web' + + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.9.1' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.1' + implementation 'com.bugsnag:bugsnag:9.9.9-test' + implementation 'com.bugsnag:bugsnag-spring:9.9.9-test' + implementation project(":scenarios") + + // required for JDK 9 and above + implementation('javax.xml.bind:jaxb-api:2.3.0') + + testImplementation group: 'junit', name: 'junit', version: '4.13.2' +} \ No newline at end of file diff --git a/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/AsyncMethodScenario.java b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/AsyncMethodScenario.java new file mode 100644 index 00000000..a7425c16 --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/AsyncMethodScenario.java @@ -0,0 +1,34 @@ +package com.bugsnag.mazerunner.scenarios; + +import com.bugsnag.Bugsnag; +import org.springframework.web.client.RestTemplate; + +/** + * Causes an unhandled exception in an async method + */ +public class AsyncMethodScenario extends Scenario { + + public AsyncMethodScenario(Bugsnag bugsnag) { + super(bugsnag); + } + + @Override + public void run() { + + // Don't report any sessions during this test + disableSessionDelivery(); + + // The rest endpoint will run an async task to throw the exception + final String uri = "http://localhost:1234/run-async-task"; + + try { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.getForObject(uri, String.class); + + // Wait for the async task to complete + Thread.sleep(2000); + } catch (Exception ex) { + // ignore + } + } +} diff --git a/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/AsyncNotifyScenario.java b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/AsyncNotifyScenario.java new file mode 100644 index 00000000..2e1d3c1f --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/AsyncNotifyScenario.java @@ -0,0 +1,34 @@ +package com.bugsnag.mazerunner.scenarios; + +import com.bugsnag.Bugsnag; +import org.springframework.web.client.RestTemplate; + +/** + * Notifies from an async method + */ +public class AsyncNotifyScenario extends Scenario { + + public AsyncNotifyScenario(Bugsnag bugsnag) { + super(bugsnag); + } + + @Override + public void run() { + + // Don't report any sessions during this test + disableSessionDelivery(); + + // The rest endpoint will run an async task to throw the exception + final String uri = "http://localhost:1234/notify-async-task"; + + try { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.getForObject(uri, String.class); + + // Wait for the async task to complete + Thread.sleep(2000); + } catch (Exception ex) { + // ignore + } + } +} diff --git a/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/AutoSessionScenario.java b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/AutoSessionScenario.java new file mode 100644 index 00000000..8058edd9 --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/AutoSessionScenario.java @@ -0,0 +1,38 @@ +package com.bugsnag.mazerunner.scenarios; + +import com.bugsnag.Bugsnag; +import org.springframework.web.client.RestTemplate; + +/** + * Causes an unhandled exception in the rest controller + */ +public class AutoSessionScenario extends Scenario { + + public AutoSessionScenario(Bugsnag bugsnag) { + super(bugsnag); + } + + @Override + public void run() { + bugsnag.setAutoCaptureSessions(true); + + final String uri = "http://localhost:1234/add-session"; + + try { + RestTemplate restTemplate = new RestTemplate(); + String result = restTemplate.getForObject(uri, String.class); + LOGGER.info("Completed auto session request: " + result); + Thread.sleep(2000); + } catch (Exception ex) { + LOGGER.error("Failed to complete request", ex); + } + + flushAllSessions(); + + try { + Thread.sleep(2000); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } +} diff --git a/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/RestControllerScenario.java b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/RestControllerScenario.java new file mode 100644 index 00000000..c9ce5da6 --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/RestControllerScenario.java @@ -0,0 +1,29 @@ +package com.bugsnag.mazerunner.scenarios; + +import com.bugsnag.Bugsnag; +import org.springframework.web.client.RestTemplate; + +/** + * Causes an unhandled exception in the rest controller + */ +public class RestControllerScenario extends Scenario { + + public RestControllerScenario(Bugsnag bugsnag) { + super(bugsnag); + } + + @Override + public void run() { + // Don't report any sessions during this test + disableSessionDelivery(); + + final String uri = "http://localhost:1234/send-unhandled-exception"; + + try { + RestTemplate restTemplate = new RestTemplate(); + String result = restTemplate.getForObject(uri, String.class); + } catch (Exception ex) { + // ignore + } + } +} diff --git a/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/ScheduledTaskExecutorScenario.java b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/ScheduledTaskExecutorScenario.java new file mode 100644 index 00000000..5dc877b2 --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/ScheduledTaskExecutorScenario.java @@ -0,0 +1,36 @@ +package com.bugsnag.mazerunner.scenarios; + +import com.bugsnag.Bugsnag; +import com.bugsnag.Report; +import com.bugsnag.callbacks.Callback; +import com.bugsnag.mazerunnerspringboot.ScheduledTaskExecutorService; +import java.util.Collection; + +public class ScheduledTaskExecutorScenario extends Scenario { + + public ScheduledTaskExecutorScenario(Bugsnag bugsnag) { + super(bugsnag); + } + + @Override + public void run() { + // Enable throwing an exception in the scheduled task + ScheduledTaskExecutorService.setSendException(); + + // Wait for the exception + try { + Thread.sleep(5000); + } catch (InterruptedException ex) { + // ignore + } + + final Collection threadnames = ScheduledTaskExecutorService.getThreadNames(); + bugsnag.notify(new RuntimeException("Whoops"), new Callback() { + @Override + public void beforeNotify(Report report) { + report.addToTab("executor", "multiThreaded", threadnames.size() > 1); + report.addToTab("executor", "names", threadnames); + } + }); + } +} diff --git a/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/ScheduledTaskScenario.java b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/ScheduledTaskScenario.java new file mode 100644 index 00000000..acf29628 --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunner/scenarios/ScheduledTaskScenario.java @@ -0,0 +1,27 @@ +package com.bugsnag.mazerunner.scenarios; + +import com.bugsnag.Bugsnag; +import com.bugsnag.mazerunnerspringboot.ScheduledTaskService; + +/** + * Causes an unhandled exception in a scheduled task + */ +public class ScheduledTaskScenario extends Scenario { + + public ScheduledTaskScenario(Bugsnag bugsnag) { + super(bugsnag); + } + + @Override + public void run() { + // Enable throwing an exception in the scheduled task + ScheduledTaskService.setThrowException(); + + // Wait for the exception + try { + Thread.sleep(5000); + } catch (InterruptedException ex) { + // ignore + } + } +} diff --git a/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/Application.java b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/Application.java new file mode 100644 index 00000000..aa57b959 --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/Application.java @@ -0,0 +1,18 @@ +package com.bugsnag.mazerunnerspringboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * Kicks off the Spring Boot application. + */ +@SpringBootApplication +@EnableScheduling +@EnableAsync +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/AsyncMethodService.java b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/AsyncMethodService.java new file mode 100644 index 00000000..5e0593a0 --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/AsyncMethodService.java @@ -0,0 +1,39 @@ +package com.bugsnag.mazerunnerspringboot; + +import com.bugsnag.Bugsnag; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +@Service +public class AsyncMethodService { + + @Autowired + Bugsnag bugsnag; + + @Async + public void doSomethingAsync() { + + // Add some thread meta data + Bugsnag.addThreadMetaData("thread", "key1", "should be cleared from meta data"); + Bugsnag.clearThreadMetaData(); + Bugsnag.addThreadMetaData("thread", "key2", "should be included in meta data"); + + try { + Thread.sleep(100); + } catch (InterruptedException ex) { + // ignore + } + + throw new RuntimeException("Unhandled exception from Async method"); + } + + @Async + public void notifyAsync() { + // Add some thread meta data + Bugsnag.addThreadMetaData("thread", "inAsyncMethod", "meta data from async method"); + + bugsnag.notify(new RuntimeException("test from async")); + } +} diff --git a/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/BugsnagAsyncConfig.java b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/BugsnagAsyncConfig.java new file mode 100644 index 00000000..3c05e2a6 --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/BugsnagAsyncConfig.java @@ -0,0 +1,21 @@ +package com.bugsnag.mazerunnerspringboot; + +import com.bugsnag.Bugsnag; +import com.bugsnag.BugsnagAsyncExceptionHandler; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurerSupport; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Configuration +@EnableScheduling +public class BugsnagAsyncConfig extends AsyncConfigurerSupport { + @Autowired + private Bugsnag bugsnag; + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return new BugsnagAsyncExceptionHandler(bugsnag); + } +} diff --git a/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/Config.java b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/Config.java new file mode 100644 index 00000000..35b7d45d --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/Config.java @@ -0,0 +1,28 @@ +package com.bugsnag.mazerunnerspringboot; + +import com.bugsnag.Bugsnag; +import com.bugsnag.BugsnagSpringConfiguration; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import(BugsnagSpringConfiguration.class) +public class Config { + + @Value("${BUGSNAG_API_KEY}") + private String bugsnagApiKey; + + @Value("${MAZERUNNER_BASE_URL}") + private String bugsnagEndpoint; + + @Bean + public Bugsnag bugsnag() { + Bugsnag bugsnag = new Bugsnag(bugsnagApiKey); + bugsnag.setEndpoints(bugsnagEndpoint + "notify", bugsnagEndpoint + "sessions"); + bugsnag.setReleaseStage("production"); + bugsnag.setAppVersion("1.0.0"); + return bugsnag; + } +} diff --git a/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/ScheduledTaskConfig.java b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/ScheduledTaskConfig.java new file mode 100644 index 00000000..532bbc77 --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/ScheduledTaskConfig.java @@ -0,0 +1,44 @@ +package com.bugsnag.mazerunnerspringboot; + +import com.bugsnag.Bugsnag; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.annotation.EnableScheduling; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +@Configuration +@EnableScheduling +public class ScheduledTaskConfig { + + @ConditionalOnProperty(name = "scheduled_executor_service_bean", havingValue = "true") + @Bean + public Executor taskScheduler() { + return Executors.newScheduledThreadPool(4); + } + + @ConditionalOnProperty(name = "other_scheduled_executor_service_bean", havingValue = "true") + @Bean + public Executor otherTaskScheduler() { + return Executors.newScheduledThreadPool(2); + } + + @ConditionalOnProperty(name = "custom_task_scheduler_bean", havingValue = "true") + @Bean + public TaskScheduler customTaskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(4); + return scheduler; + } + + @ConditionalOnProperty(name = "second_task_scheduler_bean", havingValue = "true") + @Bean(name = "taskScheduler") + public TaskScheduler secondTaskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(2); + return scheduler; + } +} diff --git a/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/ScheduledTaskExecutorService.java b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/ScheduledTaskExecutorService.java new file mode 100644 index 00000000..99c3a988 --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/ScheduledTaskExecutorService.java @@ -0,0 +1,41 @@ +package com.bugsnag.mazerunnerspringboot; + +import com.bugsnag.Bugsnag; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.beans.factory.annotation.Autowired; +import java.util.HashSet; +import java.util.Set; +import java.util.Collection; + +@Service +public class ScheduledTaskExecutorService { + + @Autowired + private Bugsnag bugsnag; + + private final Set threadNames = new HashSet(); + + private volatile boolean sendException = false; + + private static ScheduledTaskExecutorService instance; + + public ScheduledTaskExecutorService() { + instance = this; + } + + public static void setSendException() { + instance.sendException = true; + } + + public static Collection getThreadNames() { + return new HashSet<>(instance.threadNames); + } + + @Scheduled(fixedRate = 100) + public void doSomething() { + if (sendException) { + threadNames.add(Thread.currentThread().getName()); + } + } +} diff --git a/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/ScheduledTaskService.java b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/ScheduledTaskService.java new file mode 100644 index 00000000..93cb9d6c --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/ScheduledTaskService.java @@ -0,0 +1,33 @@ +package com.bugsnag.mazerunnerspringboot; + +import com.bugsnag.Bugsnag; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Service +public class ScheduledTaskService { + + private volatile boolean throwException = false; + + private static ScheduledTaskService instance; + + public ScheduledTaskService() { + instance = this; + } + + public static void setThrowException() { + instance.throwException = true; + } + + @Scheduled(fixedDelay = 3000) + public void doSomething() { + if (throwException) { + // Add some thread meta data + Bugsnag.addThreadMetaData("thread", "key1", "should be cleared from meta data"); + Bugsnag.clearThreadMetaData(); + Bugsnag.addThreadMetaData("thread", "key2", "should be included in meta data"); + + throw new RuntimeException("Unhandled exception from ScheduledTaskService"); + } + } +} diff --git a/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/TestCaseRunner.java b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/TestCaseRunner.java new file mode 100644 index 00000000..82839269 --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/TestCaseRunner.java @@ -0,0 +1,66 @@ +package com.bugsnag.mazerunnerspringboot; + +import com.bugsnag.Bugsnag; +import com.bugsnag.mazerunner.scenarios.Scenario; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.ExitCodeGenerator; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Constructor; + +@Component +public class TestCaseRunner implements CommandLineRunner, ApplicationContextAware { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestCaseRunner.class); + + private ApplicationContext ctx; + + @Autowired + private Bugsnag bugsnag; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + ctx = applicationContext; + } + + @Override + public void run(String... args) { + // Create and run the test case + LOGGER.info("Creating test case"); + String type = System.getenv("EVENT_TYPE"); + Scenario scenario = testCaseForName(type); + if (scenario != null) { + LOGGER.info("running test case " + type); + scenario.run(); + } else { + LOGGER.error("No test case found for " + type); + } + + // Exit the application + LOGGER.info("Exiting spring"); + System.exit(SpringApplication.exit(ctx, (ExitCodeGenerator) new ExitCodeGenerator() { + @Override + public int getExitCode() { + return 0; + } + })); + } + + private Scenario testCaseForName(String eventType) { + try { + Class clz = Class.forName("com.bugsnag.mazerunner.scenarios." + eventType); + Constructor constructor = clz.getConstructors()[0]; + return (Scenario) constructor.newInstance(bugsnag); + } catch (Exception ex) { + LOGGER.error("Error getting scenario", ex); + return null; + } + } +} diff --git a/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/TestRestController.java b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/TestRestController.java new file mode 100644 index 00000000..e35c0524 --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/java/com/bugsnag/mazerunnerspringboot/TestRestController.java @@ -0,0 +1,71 @@ +package com.bugsnag.mazerunnerspringboot; + +import com.bugsnag.Bugsnag; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestRestController { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestRestController.class); + + @Autowired + Bugsnag bugsnag; + + @Autowired + private AsyncMethodService asyncMethodService; + + private static TestRestController instance; + + public TestRestController() { + instance = this; + } + + public static Bugsnag getBugsnag() { + return instance.bugsnag; + } + + @RequestMapping("/send-unhandled-exception") + public String sendUnhandledException() { + throw new RuntimeException("Unhandled exception from TestRestController"); + } + + @RequestMapping("/add-session") + public String addSession() { + // A session should be automatically recorded by Bugsnag if automatic sessions are enabled + LOGGER.info("Starting a new session"); + return ""; + } + + @RequestMapping("/run-async-task") + public String runAsyncTask() { + try { + asyncMethodService.doSomethingAsync(); + } catch (Exception ex) { + // This should not happen + LOGGER.info("Saw exception from async call"); + } + + return ""; + } + + @RequestMapping("/notify-async-task") + public String notifyAsyncTask() { + + // Add some thread meta data + Bugsnag.addThreadMetaData("thread", "controllerMethod", "meta data from controller method"); + + // Notify before calling the async method + bugsnag.notify(new RuntimeException("test from before async")); + + // Call the async method (also notifies) + asyncMethodService.notifyAsync(); + + return ""; + } + +} diff --git a/features/fixtures/mazerunnerspringboot3/src/main/resources/application.properties b/features/fixtures/mazerunnerspringboot3/src/main/resources/application.properties new file mode 100644 index 00000000..7e96c7f7 --- /dev/null +++ b/features/fixtures/mazerunnerspringboot3/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=1234 diff --git a/features/fixtures/settings.gradle b/features/fixtures/settings.gradle index 664f523f..1771d139 100644 --- a/features/fixtures/settings.gradle +++ b/features/fixtures/settings.gradle @@ -2,3 +2,8 @@ include ':mazerunner', ':mazerunnerplainspring', ':mazerunnerspringboot', ':scenarios' + +if (JavaVersion.current() >= JavaVersion.VERSION_17) { + include ':mazerunnerspringboot3', + ':mazerunnerplainspring6' +} \ No newline at end of file diff --git a/features/scripts/build-plain-spring-app.sh b/features/scripts/build-plain-spring-app.sh index d2a8d4c3..0f341036 100755 --- a/features/scripts/build-plain-spring-app.sh +++ b/features/scripts/build-plain-spring-app.sh @@ -2,5 +2,10 @@ # Start Tomcat then copy a WAR file across to serve it catalina.sh start -./gradlew -p features/fixtures/mazerunnerplainspring war -cp features/fixtures/mazerunnerplainspring/build/libs/mazerunnerplainspring.war $CATALINA_HOME/webapps/ROOT.war +if [[ "${JAVA_VERSION}" == "8"* ]]; then + ./gradlew -p features/fixtures/mazerunnerplainspring war + cp features/fixtures/mazerunnerplainspring/build/libs/mazerunnerplainspring.war $CATALINA_HOME/webapps/ROOT.war +else + ./gradlew -p features/fixtures/mazerunnerplainspring6 war + cp features/fixtures/mazerunnerplainspring/build/libs/mazerunnerplainspring6.war $CATALINA_HOME/webapps/ROOT.war +fi \ No newline at end of file diff --git a/features/scripts/run-java-spring-boot-app.sh b/features/scripts/run-java-spring-boot-app.sh index f806fb9b..3014bdff 100755 --- a/features/scripts/run-java-spring-boot-app.sh +++ b/features/scripts/run-java-spring-boot-app.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash catalina.sh stop -./gradlew -p features/fixtures/mazerunnerspringboot bootRun \ No newline at end of file +if [[ "${JAVA_VERSION}" == "8"* ]]; then + ./gradlew -p features/fixtures/mazerunnerspringboot bootRun +else + ./gradlew -p features/fixtures/mazerunnerspringboot3 bootRun +fi \ No newline at end of file diff --git a/features/steps/build_steps.rb b/features/steps/build_steps.rb index 90cfe241..c1aa2528 100644 --- a/features/steps/build_steps.rb +++ b/features/steps/build_steps.rb @@ -41,6 +41,30 @@ } end +When("I run spring boot 3 {string} with the defaults") do |eventType| + steps %Q{ + And I set environment variable "MAZERUNNER_BASE_URL" to "http://localhost:9339/" + And I set environment variable "BUGSNAG_API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" + And I set environment variable "EVENT_TYPE" to "#{eventType}" + And I run the script "features/scripts/run-java-spring-boot-3-app.sh" synchronously + } +end + +Given("I run the plain spring 6 app") do + steps %Q{ + And I set environment variable "MAZERUNNER_BASE_URL" to "http://localhost:9339/" + And I set environment variable "BUGSNAG_API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" + And I run the script "features/scripts/build-plain-spring-6-app.sh" synchronously + } +end + +When("I run plain Spring 6 {string} with the defaults") do |eventType| + steps %Q{ + And I run the plain spring 6 app + And I navigate to the route "/run-scenario/#{eventType}" on port "8080" + } +end + When(/^I navigate to the route "(.*)" on port "(\d*)"/) do |route, port| steps %Q{ When I open the URL "http://localhost:#{port}#{route}" From 1a5b916b750b8a0fa224d092436fa41d5b4e03b2 Mon Sep 17 00:00:00 2001 From: David Petran Date: Fri, 24 Mar 2023 16:54:26 +0000 Subject: [PATCH 15/35] missed service definition for previous change --- docker-compose.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index dd322317..80a6cc42 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,11 @@ services: build: context: . dockerfile: dockerfiles/Dockerfile.java-common - java-mazerunner: + java8-mazerunner: build: context: . dockerfile: dockerfiles/Dockerfile.java8-mazerunner + java17-mazerunner: + build: + context: . + dockerfile: dockerfiles/Dockerfile.java17-mazerunner \ No newline at end of file From 6420d48ae6e9c89dc157b72770ce939be247fb8f Mon Sep 17 00:00:00 2001 From: David Petran Date: Mon, 27 Mar 2023 15:01:26 +0100 Subject: [PATCH 16/35] x-comatible spring notifier --- bugsnag-spring/build.gradle | 19 +- bugsnag-spring/javax/build.gradle | 35 +++ .../bugsnag/BugsnagAsyncExceptionHandler.java | 0 .../BugsnagJavaxMvcExceptionHandler.java} | 5 +- .../BugsnagScheduledTaskExceptionHandler.java | 0 .../com/bugsnag/ExceptionClassCallback.java | 0 .../com/bugsnag/JavaxMvcConfiguration.java} | 8 +- .../com/bugsnag/ScheduledTaskBeanLocator.java | 0 .../bugsnag/ScheduledTaskConfiguration.java | 0 .../bugsnag/SpringBootV2Configuration.java} | 6 +- .../bugsnag/SpringBootV2LoadedCondition.java | 26 ++ .../SpringWebJavaxMvcLoadedCondition.java} | 5 +- .../bugsnag/ScheduledTaskBeanLocatorTest.java | 81 +++++ .../ScheduledTaskConfigurationTest.java | 120 ++++++++ .../java/com/bugsnag/SpringAsyncTest.java | 90 ++++++ .../test/java/com/bugsnag/SpringMvcTest.java | 281 ++++++++++++++++++ .../com/bugsnag/SpringScheduledTaskTest.java | 95 ++++++ .../com/bugsnag/SpringTestConfiguration.java | 84 ++++++ .../src/test/java/com/bugsnag/TestUtils.java | 33 ++ .../testapp/springboot/AsyncService.java | 19 ++ .../testapp/springboot/TestConfiguration.java | 50 ++++ .../testapp/springboot/TestController.java | 75 +++++ .../springboot/TestSpringBootApplication.java | 15 + .../javax/src/test/resources/logback.xml | 13 + .../BugsnagJakartaMvcExceptionHandler.java | 49 +++ .../bugsnag/BugsnagSpringConfiguration.java | 6 +- .../com/bugsnag/JakartaMvcConfiguration.java | 35 +++ .../bugsnag/SpringBootV3Configuration.java | 71 +++++ .../bugsnag/SpringBootV3LoadedCondition.java | 26 ++ ...> SpringWebJakartaMvcLoadedCondition.java} | 9 +- .../test/java/com/bugsnag/SpringMvcTest.java | 2 +- bugsnag/build.gradle | 8 +- common.gradle | 3 + settings.gradle | 8 +- 34 files changed, 1243 insertions(+), 34 deletions(-) create mode 100644 bugsnag-spring/javax/build.gradle rename bugsnag-spring/{ => javax}/src/main/java/com/bugsnag/BugsnagAsyncExceptionHandler.java (100%) rename bugsnag-spring/{src/main/java/com/bugsnag/BugsnagMvcExceptionHandler.java => javax/src/main/java/com/bugsnag/BugsnagJavaxMvcExceptionHandler.java} (92%) rename bugsnag-spring/{ => javax}/src/main/java/com/bugsnag/BugsnagScheduledTaskExceptionHandler.java (100%) rename bugsnag-spring/{ => javax}/src/main/java/com/bugsnag/ExceptionClassCallback.java (100%) rename bugsnag-spring/{src/main/java/com/bugsnag/MvcConfiguration.java => javax/src/main/java/com/bugsnag/JavaxMvcConfiguration.java} (77%) rename bugsnag-spring/{ => javax}/src/main/java/com/bugsnag/ScheduledTaskBeanLocator.java (100%) rename bugsnag-spring/{ => javax}/src/main/java/com/bugsnag/ScheduledTaskConfiguration.java (100%) rename bugsnag-spring/{src/main/java/com/bugsnag/SpringBootConfiguration.java => javax/src/main/java/com/bugsnag/SpringBootV2Configuration.java} (94%) create mode 100644 bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootV2LoadedCondition.java rename bugsnag-spring/{src/main/java/com/bugsnag/SpringWebMvcLoadedCondition.java => javax/src/main/java/com/bugsnag/SpringWebJavaxMvcLoadedCondition.java} (76%) create mode 100644 bugsnag-spring/javax/src/test/java/com/bugsnag/ScheduledTaskBeanLocatorTest.java create mode 100644 bugsnag-spring/javax/src/test/java/com/bugsnag/ScheduledTaskConfigurationTest.java create mode 100644 bugsnag-spring/javax/src/test/java/com/bugsnag/SpringAsyncTest.java create mode 100644 bugsnag-spring/javax/src/test/java/com/bugsnag/SpringMvcTest.java create mode 100644 bugsnag-spring/javax/src/test/java/com/bugsnag/SpringScheduledTaskTest.java create mode 100644 bugsnag-spring/javax/src/test/java/com/bugsnag/SpringTestConfiguration.java create mode 100644 bugsnag-spring/javax/src/test/java/com/bugsnag/TestUtils.java create mode 100644 bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/AsyncService.java create mode 100644 bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/TestConfiguration.java create mode 100644 bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/TestController.java create mode 100644 bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/TestSpringBootApplication.java create mode 100644 bugsnag-spring/javax/src/test/resources/logback.xml create mode 100644 bugsnag-spring/src/main/java/com/bugsnag/BugsnagJakartaMvcExceptionHandler.java create mode 100644 bugsnag-spring/src/main/java/com/bugsnag/JakartaMvcConfiguration.java create mode 100644 bugsnag-spring/src/main/java/com/bugsnag/SpringBootV3Configuration.java create mode 100644 bugsnag-spring/src/main/java/com/bugsnag/SpringBootV3LoadedCondition.java rename bugsnag-spring/src/main/java/com/bugsnag/{SpringBootLoadedCondition.java => SpringWebJakartaMvcLoadedCondition.java} (62%) diff --git a/bugsnag-spring/build.gradle b/bugsnag-spring/build.gradle index b862fc87..6a5ef898 100644 --- a/bugsnag-spring/build.gradle +++ b/bugsnag-spring/build.gradle @@ -1,32 +1,29 @@ ext { - springVersion = '5.3.20' - springBootVersion = '2.5.14' + springVersion = '6.0.0' + springBootVersion = '3.0.0' } apply plugin: 'java-library' apply from: '../common.gradle' -sourceCompatibility = '1.8' -targetCompatibility = '1.8' - repositories { mavenCentral() } dependencies { - api project(':bugsnag') + api project(':bugsnag-spring:javax') testImplementation project(':bugsnag').sourceSets.test.output - compileOnly "javax.servlet:javax.servlet-api:${javaxServletApiVersion}" + compileOnly "jakarta.servlet:jakarta.servlet-api:${jakartaServletApiVersion}" 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:1.7.25" + compileOnly "org.slf4j:slf4j-api:${slf4jApiVersion}" - testImplementation "junit:junit:4.13.2" - testImplementation "javax.servlet:javax.servlet-api:${javaxServletApiVersion}" + testImplementation "junit:junit:${junitVersion}" + testImplementation "jakarta.servlet:jakarta.servlet-api:${jakartaServletApiVersion}" testImplementation "org.springframework.boot:spring-boot-starter-test:${springBootVersion}" testImplementation "org.springframework.boot:spring-boot-starter-web:${springBootVersion}" - testImplementation "org.mockito:mockito-core:5.0.0" + testImplementation "org.mockito:mockito-core:${mockitoVersion}" } diff --git a/bugsnag-spring/javax/build.gradle b/bugsnag-spring/javax/build.gradle new file mode 100644 index 00000000..3beef567 --- /dev/null +++ b/bugsnag-spring/javax/build.gradle @@ -0,0 +1,35 @@ +ext { + springVersion = '5.3.20' + springBootVersion = '2.5.14' +} + +apply plugin: 'java' +apply plugin: 'java-library' + +apply from: '../../common.gradle' + +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/src/main/java/com/bugsnag/BugsnagAsyncExceptionHandler.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagAsyncExceptionHandler.java similarity index 100% rename from bugsnag-spring/src/main/java/com/bugsnag/BugsnagAsyncExceptionHandler.java rename to bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagAsyncExceptionHandler.java diff --git a/bugsnag-spring/src/main/java/com/bugsnag/BugsnagMvcExceptionHandler.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagJavaxMvcExceptionHandler.java similarity index 92% rename from bugsnag-spring/src/main/java/com/bugsnag/BugsnagMvcExceptionHandler.java rename to bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagJavaxMvcExceptionHandler.java index 36d87856..62cbf9ea 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/BugsnagMvcExceptionHandler.java +++ b/bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagJavaxMvcExceptionHandler.java @@ -8,7 +8,6 @@ import org.springframework.web.servlet.ModelAndView; import java.util.Collections; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -20,11 +19,11 @@ * resolvers. */ @Order(Ordered.HIGHEST_PRECEDENCE) -class BugsnagMvcExceptionHandler implements HandlerExceptionResolver { +class BugsnagJavaxMvcExceptionHandler implements HandlerExceptionResolver { private final Bugsnag bugsnag; - BugsnagMvcExceptionHandler(final Bugsnag bugsnag) { + BugsnagJavaxMvcExceptionHandler(final Bugsnag bugsnag) { this.bugsnag = bugsnag; } diff --git a/bugsnag-spring/src/main/java/com/bugsnag/BugsnagScheduledTaskExceptionHandler.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagScheduledTaskExceptionHandler.java similarity index 100% rename from bugsnag-spring/src/main/java/com/bugsnag/BugsnagScheduledTaskExceptionHandler.java rename to bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagScheduledTaskExceptionHandler.java diff --git a/bugsnag-spring/src/main/java/com/bugsnag/ExceptionClassCallback.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/ExceptionClassCallback.java similarity index 100% rename from bugsnag-spring/src/main/java/com/bugsnag/ExceptionClassCallback.java rename to bugsnag-spring/javax/src/main/java/com/bugsnag/ExceptionClassCallback.java diff --git a/bugsnag-spring/src/main/java/com/bugsnag/MvcConfiguration.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/JavaxMvcConfiguration.java similarity index 77% rename from bugsnag-spring/src/main/java/com/bugsnag/MvcConfiguration.java rename to bugsnag-spring/javax/src/main/java/com/bugsnag/JavaxMvcConfiguration.java index 33bc0c91..3660227c 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/MvcConfiguration.java +++ b/bugsnag-spring/javax/src/main/java/com/bugsnag/JavaxMvcConfiguration.java @@ -10,8 +10,8 @@ * If spring-webmvc is loaded, add configuration for reporting unhandled exceptions. */ @Configuration -@Conditional(SpringWebMvcLoadedCondition.class) -class MvcConfiguration implements InitializingBean { +@Conditional(SpringWebJavaxMvcLoadedCondition.class) +class JavaxMvcConfiguration implements InitializingBean { @Autowired private Bugsnag bugsnag; @@ -21,8 +21,8 @@ class MvcConfiguration implements InitializingBean { * for uncaught exceptions thrown from request handlers. */ @Bean - BugsnagMvcExceptionHandler bugsnagHandlerExceptionResolver() { - return new BugsnagMvcExceptionHandler(bugsnag); + BugsnagJavaxMvcExceptionHandler bugsnagHandlerExceptionResolver() { + return new BugsnagJavaxMvcExceptionHandler(bugsnag); } /** diff --git a/bugsnag-spring/src/main/java/com/bugsnag/ScheduledTaskBeanLocator.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/ScheduledTaskBeanLocator.java similarity index 100% rename from bugsnag-spring/src/main/java/com/bugsnag/ScheduledTaskBeanLocator.java rename to bugsnag-spring/javax/src/main/java/com/bugsnag/ScheduledTaskBeanLocator.java diff --git a/bugsnag-spring/src/main/java/com/bugsnag/ScheduledTaskConfiguration.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/ScheduledTaskConfiguration.java similarity index 100% rename from bugsnag-spring/src/main/java/com/bugsnag/ScheduledTaskConfiguration.java rename to bugsnag-spring/javax/src/main/java/com/bugsnag/ScheduledTaskConfiguration.java diff --git a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootV2Configuration.java similarity index 94% rename from bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java rename to bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootV2Configuration.java index 275a8350..e49fee11 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java +++ b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootV2Configuration.java @@ -17,8 +17,8 @@ * If spring-boot is loaded, add configuration specific to Spring Boot */ @Configuration -@Conditional(SpringBootLoadedCondition.class) -class SpringBootConfiguration { +@Conditional(SpringBootV2LoadedCondition.class) +class SpringBootV2Configuration { @Autowired private Bugsnag bugsnag; @@ -61,7 +61,7 @@ private void addSpringRuntimeVersion(Map device) { * automatic servlet request metadata collection. */ @Bean - @Conditional(SpringWebMvcLoadedCondition.class) + @Conditional(SpringWebJavaxMvcLoadedCondition.class) ServletListenerRegistrationBean listenerRegistrationBean() { ServletListenerRegistrationBean srb = new ServletListenerRegistrationBean(); diff --git a/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootV2LoadedCondition.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootV2LoadedCondition.java new file mode 100644 index 00000000..84b7ddd3 --- /dev/null +++ b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootV2LoadedCondition.java @@ -0,0 +1,26 @@ +package com.bugsnag; + +import org.springframework.boot.SpringBootVersion; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * Check whether spring-boot is available to the application. + */ +class SpringBootV2LoadedCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, + AnnotatedTypeMetadata metadata) { + + return context.getClassLoader() != null + && context.getClassLoader().getResource("org/springframework/boot") != null + && isSpringBootV2(); + } + + private boolean isSpringBootV2() { + String bootVersion = SpringBootVersion.getVersion(); + return bootVersion != null & bootVersion.matches("2\\..+"); + } +} diff --git a/bugsnag-spring/src/main/java/com/bugsnag/SpringWebMvcLoadedCondition.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringWebJavaxMvcLoadedCondition.java similarity index 76% rename from bugsnag-spring/src/main/java/com/bugsnag/SpringWebMvcLoadedCondition.java rename to bugsnag-spring/javax/src/main/java/com/bugsnag/SpringWebJavaxMvcLoadedCondition.java index f0152181..1709dc4d 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/SpringWebMvcLoadedCondition.java +++ b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringWebJavaxMvcLoadedCondition.java @@ -1,5 +1,7 @@ package com.bugsnag; +import com.bugsnag.callbacks.JavaxServletCallback; + import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; @@ -7,12 +9,13 @@ /** * Check whether spring-webmvc is available to the application. */ -class SpringWebMvcLoadedCondition implements Condition { +class SpringWebJavaxMvcLoadedCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return context.getClassLoader() != null + && JavaxServletCallback.isAvailable() && context.getClassLoader().getResource("org/springframework/web/servlet") != null; } } diff --git a/bugsnag-spring/javax/src/test/java/com/bugsnag/ScheduledTaskBeanLocatorTest.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/ScheduledTaskBeanLocatorTest.java new file mode 100644 index 00000000..74eb856d --- /dev/null +++ b/bugsnag-spring/javax/src/test/java/com/bugsnag/ScheduledTaskBeanLocatorTest.java @@ -0,0 +1,81 @@ +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/javax/src/test/java/com/bugsnag/ScheduledTaskConfigurationTest.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/ScheduledTaskConfigurationTest.java new file mode 100644 index 00000000..5de97b42 --- /dev/null +++ b/bugsnag-spring/javax/src/test/java/com/bugsnag/ScheduledTaskConfigurationTest.java @@ -0,0 +1,120 @@ +package com.bugsnag; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +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.mockito.Mock; +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")); + } + + 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/javax/src/test/java/com/bugsnag/SpringAsyncTest.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringAsyncTest.java new file mode 100644 index 00000000..ec67ee8d --- /dev/null +++ b/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringAsyncTest.java @@ -0,0 +1,90 @@ +package com.bugsnag; + +import static com.bugsnag.TestUtils.verifyAndGetReport; +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.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 SpringTestConfiguration} 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 = 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 + 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 = 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/javax/src/test/java/com/bugsnag/SpringMvcTest.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringMvcTest.java new file mode 100644 index 00000000..52d008f6 --- /dev/null +++ b/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringMvcTest.java @@ -0,0 +1,281 @@ +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.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 SpringTestConfiguration} 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 + 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/javax/src/test/java/com/bugsnag/SpringScheduledTaskTest.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringScheduledTaskTest.java new file mode 100644 index 00000000..213facc4 --- /dev/null +++ b/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringScheduledTaskTest.java @@ -0,0 +1,95 @@ +package com.bugsnag; + +import static com.bugsnag.TestUtils.verifyAndGetReport; +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.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 SpringTestConfiguration} 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 = 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 + 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/javax/src/test/java/com/bugsnag/SpringTestConfiguration.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringTestConfiguration.java new file mode 100644 index 00000000..4b15a469 --- /dev/null +++ b/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringTestConfiguration.java @@ -0,0 +1,84 @@ +package com.bugsnag; + +import com.bugsnag.callbacks.Callback; + +import org.springframework.beans.factory.InitializingBean; +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.core.SpringVersion; + +import java.util.Map; + +/** + * Configuration to test Bugsnag with Spring v5. + */ +@Configuration +@Import({ + SpringBootV2Configuration.class, + JavaxMvcConfiguration.class, + ScheduledTaskConfiguration.class}) +public class SpringTestConfiguration implements InitializingBean { + + @Autowired + private Bugsnag bugsnag; + + /** + * Add a callback to add the version of Spring used by the application + */ + @Bean + Callback springVersionErrorCallback() { + Callback callback = new Callback() { + @Override + public void beforeNotify(Report report) { + addSpringRuntimeVersion(report.getDevice()); + } + }; + bugsnag.addCallback(callback); + return callback; + } + + @Bean + BeforeSendSession springVersionSessionCallback() { + BeforeSendSession beforeSendSession = new BeforeSendSession() { + @Override + public void beforeSendSession(SessionPayload payload) { + addSpringRuntimeVersion(payload.getDevice()); + } + }; + bugsnag.addBeforeSendSession(beforeSendSession); + return beforeSendSession; + } + + private void addSpringRuntimeVersion(Map device) { + Diagnostics.addDeviceRuntimeVersion(device, "springFramework", SpringVersion.getVersion()); + } + + @Bean + ScheduledTaskBeanLocator scheduledTaskBeanLocator() { + return new ScheduledTaskBeanLocator(); + } + + @Override + public void afterPropertiesSet() { + try { + // Exclude Tomcat logger when processing HTTP requests via a servlet. + // Regex specified to match the servlet variable parts of the logger name, e.g. + // the Spring Boot default is: + // [Tomcat].[localhost].[/].[dispatcherServlet] + // but could be something like: + // [Tomcat-1].[127.0.0.1].[/subdomain/].[customDispatcher] + BugsnagAppender.addExcludedLoggerPattern("org.apache.catalina.core.ContainerBase." + + "\\[Tomcat.*\\][.]\\[.*\\][.]\\[/.*\\][.]\\[.*\\]"); + + // Exclude Jetty logger when processing HTTP requests via the HttpChannel + BugsnagAppender.addExcludedLoggerPattern("org.eclipse.jetty.server.HttpChannel"); + + // Exclude Undertow logger when processing HTTP requests + BugsnagAppender.addExcludedLoggerPattern("io.undertow.request"); + } catch (NoClassDefFoundError ignored) { + // logback was not in classpath, ignore throwable to allow further initialisation + } + } +} diff --git a/bugsnag-spring/javax/src/test/java/com/bugsnag/TestUtils.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/TestUtils.java new file mode 100644 index 00000000..4bc70115 --- /dev/null +++ b/bugsnag-spring/javax/src/test/java/com/bugsnag/TestUtils.java @@ -0,0 +1,33 @@ +package com.bugsnag; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import com.bugsnag.delivery.Delivery; +import com.bugsnag.serialization.Serializer; + +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; + +import java.util.Map; + +class TestUtils { + + /** + * Verify that a report was received, then capture and return that report + */ + static Report verifyAndGetReport(Delivery delivery) { + ArgumentCaptor notificationCaptor = + ArgumentCaptor.forClass(Notification.class); + verify(delivery, timeout(100).times(1)).deliver( + any(Serializer.class), + notificationCaptor.capture(), + anyMapOf(String.class, String.class)); + return notificationCaptor.getValue().getEvents().get(0); + } + + static Map anyMapOf(Class keyClazz, Class valueClazz) { + return ArgumentMatchers.anyMap(); + } +} diff --git a/bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/AsyncService.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/AsyncService.java new file mode 100644 index 00000000..54376bac --- /dev/null +++ b/bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/AsyncService.java @@ -0,0 +1,19 @@ +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/javax/src/test/java/com/bugsnag/testapp/springboot/TestConfiguration.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/TestConfiguration.java new file mode 100644 index 00000000..03457b5b --- /dev/null +++ b/bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/TestConfiguration.java @@ -0,0 +1,50 @@ +package com.bugsnag.testapp.springboot; + +import com.bugsnag.Bugsnag; +import com.bugsnag.BugsnagAsyncExceptionHandler; +import com.bugsnag.SpringTestConfiguration; + +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(SpringTestConfiguration.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/javax/src/test/java/com/bugsnag/testapp/springboot/TestController.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/TestController.java new file mode 100644 index 00000000..9d0091b5 --- /dev/null +++ b/bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/TestController.java @@ -0,0 +1,75 @@ +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/javax/src/test/java/com/bugsnag/testapp/springboot/TestSpringBootApplication.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/TestSpringBootApplication.java new file mode 100644 index 00000000..f02cde84 --- /dev/null +++ b/bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/TestSpringBootApplication.java @@ -0,0 +1,15 @@ +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/javax/src/test/resources/logback.xml b/bugsnag-spring/javax/src/test/resources/logback.xml new file mode 100644 index 00000000..073f1525 --- /dev/null +++ b/bugsnag-spring/javax/src/test/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + + apiKey + + + + + + + \ No newline at end of file diff --git a/bugsnag-spring/src/main/java/com/bugsnag/BugsnagJakartaMvcExceptionHandler.java b/bugsnag-spring/src/main/java/com/bugsnag/BugsnagJakartaMvcExceptionHandler.java new file mode 100644 index 00000000..ac41ca01 --- /dev/null +++ b/bugsnag-spring/src/main/java/com/bugsnag/BugsnagJakartaMvcExceptionHandler.java @@ -0,0 +1,49 @@ +package com.bugsnag; + +import com.bugsnag.HandledState.SeverityReasonType; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +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; + +/** + * 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 BugsnagJakartaMvcExceptionHandler implements HandlerExceptionResolver { + + private final Bugsnag bugsnag; + + BugsnagJakartaMvcExceptionHandler(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/main/java/com/bugsnag/BugsnagSpringConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java index 43cdaa99..951206c7 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java +++ b/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java @@ -16,8 +16,10 @@ */ @Configuration @Import({ - SpringBootConfiguration.class, - MvcConfiguration.class, + SpringBootV2Configuration.class, + SpringBootV3Configuration.class, + JavaxMvcConfiguration.class, + JakartaMvcConfiguration.class, ScheduledTaskConfiguration.class}) public class BugsnagSpringConfiguration implements InitializingBean { diff --git a/bugsnag-spring/src/main/java/com/bugsnag/JakartaMvcConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/JakartaMvcConfiguration.java new file mode 100644 index 00000000..d3385aa6 --- /dev/null +++ b/bugsnag-spring/src/main/java/com/bugsnag/JakartaMvcConfiguration.java @@ -0,0 +1,35 @@ +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(SpringWebJakartaMvcLoadedCondition.class) +class JakartaMvcConfiguration implements InitializingBean { + + @Autowired + private Bugsnag bugsnag; + + /** + * Register an exception resolver to send unhandled reports to Bugsnag + * for uncaught exceptions thrown from request handlers. + */ + @Bean + BugsnagJakartaMvcExceptionHandler bugsnagHandlerExceptionResolver() { + return new BugsnagJakartaMvcExceptionHandler(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/main/java/com/bugsnag/SpringBootV3Configuration.java b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootV3Configuration.java new file mode 100644 index 00000000..89b5a6ef --- /dev/null +++ b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootV3Configuration.java @@ -0,0 +1,71 @@ +package com.bugsnag; + +import com.bugsnag.callbacks.Callback; +import com.bugsnag.servlet.jakarta.BugsnagServletRequestListener; + +import jakarta.servlet.ServletRequestListener; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootVersion; +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 java.util.Map; + +/** + * If spring-boot is loaded, add configuration specific to Spring Boot + */ +@Configuration +@Conditional(SpringBootV3LoadedCondition.class) +class SpringBootV3Configuration { + + @Autowired + private Bugsnag bugsnag; + + /** + * Add a callback to add the version of Spring Boot used by the application. + */ + @Bean + Callback springBootVersionErrorCallback() { + Callback callback = new Callback() { + @Override + public void beforeNotify(Report report) { + addSpringRuntimeVersion(report.getDevice()); + } + }; + bugsnag.addCallback(callback); + return callback; + } + + @Bean + BeforeSendSession springBootVersionSessionCallback() { + BeforeSendSession beforeSendSession = new BeforeSendSession() { + @Override + public void beforeSendSession(SessionPayload payload) { + addSpringRuntimeVersion(payload.getDevice()); + } + }; + bugsnag.addBeforeSendSession(beforeSendSession); + return beforeSendSession; + } + + private void addSpringRuntimeVersion(Map device) { + Diagnostics.addDeviceRuntimeVersion(device, "springBoot", SpringBootVersion.getVersion()); + } + + /** + * 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(SpringWebJakartaMvcLoadedCondition.class) + ServletListenerRegistrationBean listenerRegistrationBean() { + ServletListenerRegistrationBean srb = + new ServletListenerRegistrationBean(); + srb.setListener(new BugsnagServletRequestListener()); + return srb; + } +} diff --git a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootV3LoadedCondition.java b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootV3LoadedCondition.java new file mode 100644 index 00000000..9857468a --- /dev/null +++ b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootV3LoadedCondition.java @@ -0,0 +1,26 @@ +package com.bugsnag; + +import org.springframework.boot.SpringBootVersion; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * Check whether spring-boot is available to the application. + */ +class SpringBootV3LoadedCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, + AnnotatedTypeMetadata metadata) { + + return context.getClassLoader() != null + && context.getClassLoader().getResource("org/springframework/boot") != null + && isSpringBootV3(); + } + + private boolean isSpringBootV3() { + String bootVersion = SpringBootVersion.getVersion(); + return bootVersion != null & bootVersion.matches("3\\..+"); + } +} diff --git a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootLoadedCondition.java b/bugsnag-spring/src/main/java/com/bugsnag/SpringWebJakartaMvcLoadedCondition.java similarity index 62% rename from bugsnag-spring/src/main/java/com/bugsnag/SpringBootLoadedCondition.java rename to bugsnag-spring/src/main/java/com/bugsnag/SpringWebJakartaMvcLoadedCondition.java index 47611314..7052a899 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootLoadedCondition.java +++ b/bugsnag-spring/src/main/java/com/bugsnag/SpringWebJakartaMvcLoadedCondition.java @@ -1,18 +1,21 @@ package com.bugsnag; +import com.bugsnag.callbacks.JakartaServletCallback; + import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; /** - * Check whether spring-boot is available to the application. + * Check whether spring-webmvc is available to the application. */ -class SpringBootLoadedCondition implements Condition { +class SpringWebJakartaMvcLoadedCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return context.getClassLoader() != null - && context.getClassLoader().getResource("org/springframework/boot") != null; + && JakartaServletCallback.isAvailable() + && context.getClassLoader().getResource("org/springframework/web/servlet") != null; } } diff --git a/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java b/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java index 6c84bbc6..db844e14 100644 --- a/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java +++ b/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java @@ -28,7 +28,7 @@ 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.boot.test.web.server.LocalServerPort; import org.springframework.core.SpringVersion; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; diff --git a/bugsnag/build.gradle b/bugsnag/build.gradle index d91e4539..91c94f94 100644 --- a/bugsnag/build.gradle +++ b/bugsnag/build.gradle @@ -14,18 +14,18 @@ repositories { dependencies { api "com.fasterxml.jackson.core:jackson-databind:2.13.3" - api "org.slf4j:slf4j-api:1.7.25" + 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:4.13.2" - testImplementation "org.slf4j:log4j-over-slf4j:1.7.25" + 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:5.0.0" + testImplementation "org.mockito:mockito-core:${mockitoVersion}" testImplementation("ch.qos.logback:logback-classic:${logbackVersion}") { exclude group: "org.slf4j" } diff --git a/common.gradle b/common.gradle index 00223406..347151e1 100644 --- a/common.gradle +++ b/common.gradle @@ -2,6 +2,9 @@ ext { javaxServletApiVersion = "3.1.0" jakartaServletApiVersion = "5.0.0" logbackVersion = "1.2.3" + slf4jApiVersion = "1.7.25" + junitVersion = "4.13.2" + mockitoVersion = "5.0.0" } if (JavaVersion.current().isJava8Compatible()) { diff --git a/settings.gradle b/settings.gradle index 596924ce..2bdcece8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,6 @@ include ':bugsnag', - ':bugsnag-spring' + ':bugsnag-spring', + ':bugsnag-spring:javax' include ':examples:simple', ':examples:servlet-javax', @@ -10,4 +11,7 @@ include ':examples:simple', // jakarta servlet example requires java 11 compatibility for gretty plugin if (JavaVersion.current().isJava11Compatible()) { include ':examples:servlet-jakarta' -} \ No newline at end of file +} +include 'bugsnag-spring:javax' +findProject(':bugsnag-spring:javax')?.name = 'javax' + From ad58df2cc7fcfe551e2e16c5fe22b3a8a7949e35 Mon Sep 17 00:00:00 2001 From: David Petran Date: Tue, 28 Mar 2023 11:23:48 +0100 Subject: [PATCH 17/35] gradle build to contruct jar correctly, incr version number to ensure targeting new bugsnag.jar --- bugsnag-spring/build.gradle | 18 ++++++++++++++++-- .../src/main/java/com/bugsnag/Notifier.java | 2 +- examples/spring-web/build.gradle | 2 ++ examples/spring/build.gradle | 2 ++ gradle.properties | 2 +- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/bugsnag-spring/build.gradle b/bugsnag-spring/build.gradle index 6a5ef898..f7272f2e 100644 --- a/bugsnag-spring/build.gradle +++ b/bugsnag-spring/build.gradle @@ -12,8 +12,8 @@ repositories { } dependencies { - api project(':bugsnag-spring:javax') - testImplementation project(':bugsnag').sourceSets.test.output + compileOnly project(':bugsnag-spring:javax') + api project(':bugsnag') compileOnly "jakarta.servlet:jakarta.servlet-api:${jakartaServletApiVersion}" compileOnly "org.springframework:spring-webmvc:${springVersion}" @@ -21,9 +21,23 @@ dependencies { compileOnly "ch.qos.logback:logback-core:${logbackVersion}" compileOnly "org.slf4j:slf4j-api:${slf4jApiVersion}" + testImplementation project(':bugsnag').sourceSets.test.output + testImplementation project(':bugsnag-spring:javax') + testImplementation project(':bugsnag') testImplementation "junit:junit:${junitVersion}" testImplementation "jakarta.servlet:jakarta.servlet-api:${jakartaServletApiVersion}" testImplementation "org.springframework.boot:spring-boot-starter-test:${springBootVersion}" testImplementation "org.springframework.boot:spring-boot-starter-web:${springBootVersion}" testImplementation "org.mockito:mockito-core:${mockitoVersion}" } + +evaluationDependsOnChildren() + +tasks['jar'].mustRunAfter(project(':bugsnag-spring:javax').tasks['jar']) + +jar { + from project.sourceSets.main.allSource + from project(':bugsnag-spring:javax').configurations.archives.artifacts.files.collect { file -> + zipTree(file) + } +} diff --git a/bugsnag/src/main/java/com/bugsnag/Notifier.java b/bugsnag/src/main/java/com/bugsnag/Notifier.java index e3f3c608..dbd0b5b5 100644 --- a/bugsnag/src/main/java/com/bugsnag/Notifier.java +++ b/bugsnag/src/main/java/com/bugsnag/Notifier.java @@ -5,7 +5,7 @@ class Notifier { private static final String NOTIFIER_NAME = "Bugsnag Java"; - private static final String NOTIFIER_VERSION = "3.6.4"; + private static final String NOTIFIER_VERSION = "4.0.0-beta"; private static final String NOTIFIER_URL = "https://github.com/bugsnag/bugsnag-java"; private String notifierName = NOTIFIER_NAME; diff --git a/examples/spring-web/build.gradle b/examples/spring-web/build.gradle index ef6c3b41..00413b4f 100644 --- a/examples/spring-web/build.gradle +++ b/examples/spring-web/build.gradle @@ -21,6 +21,8 @@ repositories { dependencies { implementation "org.springframework.boot:spring-boot-starter-web" + implementation project(":bugsnag") implementation project(":bugsnag-spring") + implementation project(":bugsnag-spring:javax") } diff --git a/examples/spring/build.gradle b/examples/spring/build.gradle index 65d62904..1dbacd45 100644 --- a/examples/spring/build.gradle +++ b/examples/spring/build.gradle @@ -21,5 +21,7 @@ repositories { dependencies { implementation "org.springframework.boot:spring-boot-starter" + implementation project(":bugsnag") implementation project(":bugsnag-spring") + implementation project(":bugsnag-spring:javax") } diff --git a/gradle.properties b/gradle.properties index 24d93332..3eaf97e4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=3.6.4 +version=4.0.0-beta group=com.bugsnag # Default properties From 88ff740117e8d74d8529dded3efd965c9718970d Mon Sep 17 00:00:00 2001 From: David Petran Date: Tue, 28 Mar 2023 17:03:46 +0100 Subject: [PATCH 18/35] fix for not able to build bugsnag-spring project on java8 machine --- dockerfiles/Dockerfile.java8-mazerunner | 23 +++++++++++++++++-- .../mazerunnerplainspring6/build.gradle | 6 ++--- .../mazerunnerspringboot3/build.gradle | 2 -- features/scripts/assemble-fixtures.sh | 10 ++++---- features/scripts/build-plain-spring-app.sh | 2 +- 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/dockerfiles/Dockerfile.java8-mazerunner b/dockerfiles/Dockerfile.java8-mazerunner index 2ca0aad8..55d8bb29 100644 --- a/dockerfiles/Dockerfile.java8-mazerunner +++ b/dockerfiles/Dockerfile.java8-mazerunner @@ -1,9 +1,27 @@ -FROM tomcat:9.0.56-jdk8 +FROM eclipse-temurin:17 as builder + WORKDIR /app +# Force download of gradle zip early to avoid repeating +# if Docker cache is invalidated by branch changes. +COPY gradlew gradle.properties /app/ +COPY gradle/ /app/gradle/ +ENV GRADLE_OPTS="-Dorg.gradle.daemon=false" +COPY settings.gradle /app/ +RUN ./gradlew + +# Copy repo into docker +COPY . /app + +RUN features/scripts/assemble-fixtures.sh + +FROM tomcat:9.0.56-jdk8 + RUN apt-get update && DEBIAN_FRONTEND=noninteractive \ apt-get install -y -q docker-compose bundler libcurl4-openssl-dev +WORKDIR /app + # Force download of gradle zip early to avoid repeating # if Docker cache is invalidated by branch changes. COPY gradlew gradle.properties /app/ @@ -12,9 +30,10 @@ ENV GRADLE_OPTS="-Dorg.gradle.daemon=false" COPY settings.gradle /app/ RUN ./gradlew -# Copy repo into docker COPY . /app +COPY --from=builder /app/features/fixtures/libs/* features/fixtures/libs/ + # Setup mazerunner RUN gem install bundler:1.16.5 RUN bundle install diff --git a/features/fixtures/mazerunnerplainspring6/build.gradle b/features/fixtures/mazerunnerplainspring6/build.gradle index e25fc418..cb43c4c4 100644 --- a/features/fixtures/mazerunnerplainspring6/build.gradle +++ b/features/fixtures/mazerunnerplainspring6/build.gradle @@ -2,7 +2,7 @@ plugins { id "war" } -group 'com.bugsnag.mazerunnerplainspring6' +group 'com.bugsnag.mazerunnerplainspring' repositories { mavenCentral() @@ -16,13 +16,11 @@ dependencies { implementation("jakarta.servlet:jakarta.servlet-api:5.0.0") implementation("ch.qos.logback:logback-classic:1.2.3") - implementation("com.fasterxml.jackson.core:jackson-annotations:2.9.1") - implementation("com.fasterxml.jackson.core:jackson-databind:2.9.1") implementation("com.bugsnag:bugsnag:9.9.9-test") implementation("com.bugsnag:bugsnag-spring:9.9.9-test") implementation project(":scenarios") } war { - archiveName = 'mazerunnerplainspring6.war' + archiveName = 'mazerunnerplainspring.war' } \ No newline at end of file diff --git a/features/fixtures/mazerunnerspringboot3/build.gradle b/features/fixtures/mazerunnerspringboot3/build.gradle index 7e8b4c36..a5bf660c 100644 --- a/features/fixtures/mazerunnerspringboot3/build.gradle +++ b/features/fixtures/mazerunnerspringboot3/build.gradle @@ -28,8 +28,6 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'com.fasterxml.jackson.core:jackson-annotations:2.9.1' - implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.1' implementation 'com.bugsnag:bugsnag:9.9.9-test' implementation 'com.bugsnag:bugsnag-spring:9.9.9-test' implementation project(":scenarios") diff --git a/features/scripts/assemble-fixtures.sh b/features/scripts/assemble-fixtures.sh index 9dafd461..47a54759 100755 --- a/features/scripts/assemble-fixtures.sh +++ b/features/scripts/assemble-fixtures.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash -mkdir -p features/fixtures/libs -./gradlew bugsnag:assemble bugsnag-spring:assemble -Pversion=9.9.9-test -cp bugsnag/build/libs/bugsnag-9.9.9-test.jar features/fixtures/libs/bugsnag-9.9.9-test.jar -cp bugsnag-spring/build/libs/bugsnag-spring-9.9.9-test.jar features/fixtures/libs/bugsnag-spring-9.9.9-test.jar +if [ ! -d "features/fixtures/libs" ]; then + mkdir -p features/fixtures/libs + ./gradlew bugsnag:assemble bugsnag-spring:javax:assemble bugsnag-spring:assemble -Pversion=9.9.9-test + cp bugsnag/build/libs/bugsnag-9.9.9-test.jar features/fixtures/libs/bugsnag-9.9.9-test.jar + cp bugsnag-spring/build/libs/bugsnag-spring-9.9.9-test.jar features/fixtures/libs/bugsnag-spring-9.9.9-test.jar +fi \ No newline at end of file diff --git a/features/scripts/build-plain-spring-app.sh b/features/scripts/build-plain-spring-app.sh index 0f341036..d74c0422 100755 --- a/features/scripts/build-plain-spring-app.sh +++ b/features/scripts/build-plain-spring-app.sh @@ -7,5 +7,5 @@ if [[ "${JAVA_VERSION}" == "8"* ]]; then cp features/fixtures/mazerunnerplainspring/build/libs/mazerunnerplainspring.war $CATALINA_HOME/webapps/ROOT.war else ./gradlew -p features/fixtures/mazerunnerplainspring6 war - cp features/fixtures/mazerunnerplainspring/build/libs/mazerunnerplainspring6.war $CATALINA_HOME/webapps/ROOT.war + cp features/fixtures/mazerunnerplainspring/build/libs/mazerunnerplainspring.war $CATALINA_HOME/webapps/ROOT.war fi \ No newline at end of file From ba0a0e534e1cf7a80040f7bbf3075afdaeadf998 Mon Sep 17 00:00:00 2001 From: David Petran Date: Thu, 30 Mar 2023 10:37:58 +0100 Subject: [PATCH 19/35] fix incorrect java version for bugsnagspringconfiguration in java 8 --- .../com/bugsnag/BugsnagImportSelector.java | 54 ++++++++++++ .../bugsnag/BugsnagSpringConfiguration.java | 11 +-- ...tion.java => SpringBootConfiguration.java} | 28 +------ .../bugsnag/SpringBootJavaxConfiguration.java | 33 ++++++++ .../bugsnag/SpringBootLoadedCondition.java} | 11 +-- .../bugsnag/SpringBootV2LoadedCondition.java | 26 ------ .../SpringWebJakartaMvcLoadedCondition.java | 0 .../java/com/bugsnag/SpringAsyncTest.java | 2 +- .../test/java/com/bugsnag/SpringMvcTest.java | 2 +- .../com/bugsnag/SpringScheduledTaskTest.java | 2 +- .../com/bugsnag/SpringTestConfiguration.java | 84 ------------------- .../testapp/springboot/TestConfiguration.java | 4 +- .../SpringBootJakartaConfiguration.java | 32 +++++++ .../bugsnag/SpringBootV3Configuration.java | 71 ---------------- .../test/java/com/bugsnag/SpringMvcTest.java | 2 +- .../com/bugsnag/SpringScheduledTaskTest.java | 2 +- 16 files changed, 130 insertions(+), 234 deletions(-) create mode 100644 bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagImportSelector.java rename bugsnag-spring/{ => javax}/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java (91%) rename bugsnag-spring/javax/src/main/java/com/bugsnag/{SpringBootV2Configuration.java => SpringBootConfiguration.java} (51%) create mode 100644 bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootJavaxConfiguration.java rename bugsnag-spring/{src/main/java/com/bugsnag/SpringBootV3LoadedCondition.java => javax/src/main/java/com/bugsnag/SpringBootLoadedCondition.java} (60%) delete mode 100644 bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootV2LoadedCondition.java rename bugsnag-spring/{ => javax}/src/main/java/com/bugsnag/SpringWebJakartaMvcLoadedCondition.java (100%) delete mode 100644 bugsnag-spring/javax/src/test/java/com/bugsnag/SpringTestConfiguration.java create mode 100644 bugsnag-spring/src/main/java/com/bugsnag/SpringBootJakartaConfiguration.java delete mode 100644 bugsnag-spring/src/main/java/com/bugsnag/SpringBootV3Configuration.java diff --git a/bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagImportSelector.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagImportSelector.java new file mode 100644 index 00000000..6d6e91da --- /dev/null +++ b/bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagImportSelector.java @@ -0,0 +1,54 @@ +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("."); + if (firstDot == -1) { + return 0; + } + + String majorVersion = version.substring(0, firstDot); + try { + return Integer.parseInt(majorVersion); + } catch (NumberFormatException nfe) { + return 0; + } + } +} diff --git a/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java similarity index 91% rename from bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java rename to bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java index 951206c7..83206060 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java +++ b/bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java @@ -11,18 +11,9 @@ import java.util.Map; -/** - * Configuration to integrate Bugsnag with Spring. - */ @Configuration -@Import({ - SpringBootV2Configuration.class, - SpringBootV3Configuration.class, - JavaxMvcConfiguration.class, - JakartaMvcConfiguration.class, - ScheduledTaskConfiguration.class}) +@Import(BugsnagImportSelector.class) public class BugsnagSpringConfiguration implements InitializingBean { - @Autowired private Bugsnag bugsnag; diff --git a/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootV2Configuration.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootConfiguration.java similarity index 51% rename from bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootV2Configuration.java rename to bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootConfiguration.java index e49fee11..f8f71ab0 100644 --- a/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootV2Configuration.java +++ b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootConfiguration.java @@ -1,25 +1,14 @@ package com.bugsnag; import com.bugsnag.callbacks.Callback; -import com.bugsnag.servlet.javax.BugsnagServletRequestListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootVersion; -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 java.util.Map; -import javax.servlet.ServletRequestListener; - -/** - * If spring-boot is loaded, add configuration specific to Spring Boot - */ -@Configuration -@Conditional(SpringBootV2LoadedCondition.class) -class SpringBootV2Configuration { +public class SpringBootConfiguration { @Autowired private Bugsnag bugsnag; @@ -53,19 +42,4 @@ public void beforeSendSession(SessionPayload payload) { private void addSpringRuntimeVersion(Map device) { Diagnostics.addDeviceRuntimeVersion(device, "springBoot", SpringBootVersion.getVersion()); } - - /** - * 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(SpringWebJavaxMvcLoadedCondition.class) - ServletListenerRegistrationBean listenerRegistrationBean() { - ServletListenerRegistrationBean srb = - new ServletListenerRegistrationBean(); - srb.setListener(new BugsnagServletRequestListener()); - return srb; - } } diff --git a/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootJavaxConfiguration.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootJavaxConfiguration.java new file mode 100644 index 00000000..6183aafa --- /dev/null +++ b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootJavaxConfiguration.java @@ -0,0 +1,33 @@ +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(SpringWebJavaxMvcLoadedCondition.class) + ServletListenerRegistrationBean listenerRegistrationBean() { + ServletListenerRegistrationBean srb = + new ServletListenerRegistrationBean(); + srb.setListener(new BugsnagServletRequestListener()); + return srb; + } +} diff --git a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootV3LoadedCondition.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootLoadedCondition.java similarity index 60% rename from bugsnag-spring/src/main/java/com/bugsnag/SpringBootV3LoadedCondition.java rename to bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootLoadedCondition.java index 9857468a..4911667c 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootV3LoadedCondition.java +++ b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootLoadedCondition.java @@ -1,6 +1,5 @@ package com.bugsnag; -import org.springframework.boot.SpringBootVersion; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; @@ -8,19 +7,13 @@ /** * Check whether spring-boot is available to the application. */ -class SpringBootV3LoadedCondition implements Condition { +class SpringBootLoadedCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return context.getClassLoader() != null - && context.getClassLoader().getResource("org/springframework/boot") != null - && isSpringBootV3(); - } - - private boolean isSpringBootV3() { - String bootVersion = SpringBootVersion.getVersion(); - return bootVersion != null & bootVersion.matches("3\\..+"); + && context.getClassLoader().getResource("org/springframework/boot") != null; } } diff --git a/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootV2LoadedCondition.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootV2LoadedCondition.java deleted file mode 100644 index 84b7ddd3..00000000 --- a/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootV2LoadedCondition.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.bugsnag; - -import org.springframework.boot.SpringBootVersion; -import org.springframework.context.annotation.Condition; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.type.AnnotatedTypeMetadata; - -/** - * Check whether spring-boot is available to the application. - */ -class SpringBootV2LoadedCondition implements Condition { - - @Override - public boolean matches(ConditionContext context, - AnnotatedTypeMetadata metadata) { - - return context.getClassLoader() != null - && context.getClassLoader().getResource("org/springframework/boot") != null - && isSpringBootV2(); - } - - private boolean isSpringBootV2() { - String bootVersion = SpringBootVersion.getVersion(); - return bootVersion != null & bootVersion.matches("2\\..+"); - } -} diff --git a/bugsnag-spring/src/main/java/com/bugsnag/SpringWebJakartaMvcLoadedCondition.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringWebJakartaMvcLoadedCondition.java similarity index 100% rename from bugsnag-spring/src/main/java/com/bugsnag/SpringWebJakartaMvcLoadedCondition.java rename to bugsnag-spring/javax/src/main/java/com/bugsnag/SpringWebJakartaMvcLoadedCondition.java diff --git a/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringAsyncTest.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringAsyncTest.java index ec67ee8d..42a91df2 100644 --- a/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringAsyncTest.java +++ b/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringAsyncTest.java @@ -23,7 +23,7 @@ /** * Test that a Spring Boot application configured with the - * {@link SpringTestConfiguration} performs as expected. + * {@link BugsnagSpringConfiguration} performs as expected. */ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestSpringBootApplication.class) diff --git a/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringMvcTest.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringMvcTest.java index 52d008f6..3b7e6361 100644 --- a/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringMvcTest.java +++ b/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringMvcTest.java @@ -40,7 +40,7 @@ /** * Test that a Spring Boot application configured with the - * {@link SpringTestConfiguration} performs as expected. + * {@link BugsnagSpringConfiguration} performs as expected. */ @RunWith(SpringRunner.class) @SpringBootTest( diff --git a/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringScheduledTaskTest.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringScheduledTaskTest.java index 213facc4..944b95e9 100644 --- a/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringScheduledTaskTest.java +++ b/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringScheduledTaskTest.java @@ -29,7 +29,7 @@ /** * Test that a Spring Boot application configured with the - * {@link SpringTestConfiguration} performs as expected. + * {@link BugsnagSpringConfiguration} performs as expected. */ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestSpringBootApplication.class) diff --git a/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringTestConfiguration.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringTestConfiguration.java deleted file mode 100644 index 4b15a469..00000000 --- a/bugsnag-spring/javax/src/test/java/com/bugsnag/SpringTestConfiguration.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.bugsnag; - -import com.bugsnag.callbacks.Callback; - -import org.springframework.beans.factory.InitializingBean; -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.core.SpringVersion; - -import java.util.Map; - -/** - * Configuration to test Bugsnag with Spring v5. - */ -@Configuration -@Import({ - SpringBootV2Configuration.class, - JavaxMvcConfiguration.class, - ScheduledTaskConfiguration.class}) -public class SpringTestConfiguration implements InitializingBean { - - @Autowired - private Bugsnag bugsnag; - - /** - * Add a callback to add the version of Spring used by the application - */ - @Bean - Callback springVersionErrorCallback() { - Callback callback = new Callback() { - @Override - public void beforeNotify(Report report) { - addSpringRuntimeVersion(report.getDevice()); - } - }; - bugsnag.addCallback(callback); - return callback; - } - - @Bean - BeforeSendSession springVersionSessionCallback() { - BeforeSendSession beforeSendSession = new BeforeSendSession() { - @Override - public void beforeSendSession(SessionPayload payload) { - addSpringRuntimeVersion(payload.getDevice()); - } - }; - bugsnag.addBeforeSendSession(beforeSendSession); - return beforeSendSession; - } - - private void addSpringRuntimeVersion(Map device) { - Diagnostics.addDeviceRuntimeVersion(device, "springFramework", SpringVersion.getVersion()); - } - - @Bean - ScheduledTaskBeanLocator scheduledTaskBeanLocator() { - return new ScheduledTaskBeanLocator(); - } - - @Override - public void afterPropertiesSet() { - try { - // Exclude Tomcat logger when processing HTTP requests via a servlet. - // Regex specified to match the servlet variable parts of the logger name, e.g. - // the Spring Boot default is: - // [Tomcat].[localhost].[/].[dispatcherServlet] - // but could be something like: - // [Tomcat-1].[127.0.0.1].[/subdomain/].[customDispatcher] - BugsnagAppender.addExcludedLoggerPattern("org.apache.catalina.core.ContainerBase." - + "\\[Tomcat.*\\][.]\\[.*\\][.]\\[/.*\\][.]\\[.*\\]"); - - // Exclude Jetty logger when processing HTTP requests via the HttpChannel - BugsnagAppender.addExcludedLoggerPattern("org.eclipse.jetty.server.HttpChannel"); - - // Exclude Undertow logger when processing HTTP requests - BugsnagAppender.addExcludedLoggerPattern("io.undertow.request"); - } catch (NoClassDefFoundError ignored) { - // logback was not in classpath, ignore throwable to allow further initialisation - } - } -} diff --git a/bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/TestConfiguration.java b/bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/TestConfiguration.java index 03457b5b..c1691243 100644 --- a/bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/TestConfiguration.java +++ b/bugsnag-spring/javax/src/test/java/com/bugsnag/testapp/springboot/TestConfiguration.java @@ -2,7 +2,7 @@ import com.bugsnag.Bugsnag; import com.bugsnag.BugsnagAsyncExceptionHandler; -import com.bugsnag.SpringTestConfiguration; +import com.bugsnag.BugsnagSpringConfiguration; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.beans.factory.annotation.Autowired; @@ -20,7 +20,7 @@ * that will be used for real Spring bugsnag integration. */ @Configuration -@Import(SpringTestConfiguration.class) +@Import(BugsnagSpringConfiguration.class) public class TestConfiguration extends AsyncConfigurerSupport implements SchedulingConfigurer { @Autowired(required = false) diff --git a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootJakartaConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootJakartaConfiguration.java new file mode 100644 index 00000000..ff0396ee --- /dev/null +++ b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootJakartaConfiguration.java @@ -0,0 +1,32 @@ +package com.bugsnag; + +import com.bugsnag.servlet.jakarta.BugsnagServletRequestListener; + +import jakarta.servlet.ServletRequestListener; +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; + +/** + * If spring-boot is loaded, add configuration specific to Spring Boot + */ +@Configuration +@Conditional(SpringBootLoadedCondition.class) +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 + * automatic servlet request metadata collection. + */ + @Bean + @Conditional(SpringWebJakartaMvcLoadedCondition.class) + ServletListenerRegistrationBean listenerRegistrationBean() { + ServletListenerRegistrationBean srb = + new ServletListenerRegistrationBean(); + srb.setListener(new BugsnagServletRequestListener()); + return srb; + } +} diff --git a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootV3Configuration.java b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootV3Configuration.java deleted file mode 100644 index 89b5a6ef..00000000 --- a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootV3Configuration.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.bugsnag; - -import com.bugsnag.callbacks.Callback; -import com.bugsnag.servlet.jakarta.BugsnagServletRequestListener; - -import jakarta.servlet.ServletRequestListener; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringBootVersion; -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 java.util.Map; - -/** - * If spring-boot is loaded, add configuration specific to Spring Boot - */ -@Configuration -@Conditional(SpringBootV3LoadedCondition.class) -class SpringBootV3Configuration { - - @Autowired - private Bugsnag bugsnag; - - /** - * Add a callback to add the version of Spring Boot used by the application. - */ - @Bean - Callback springBootVersionErrorCallback() { - Callback callback = new Callback() { - @Override - public void beforeNotify(Report report) { - addSpringRuntimeVersion(report.getDevice()); - } - }; - bugsnag.addCallback(callback); - return callback; - } - - @Bean - BeforeSendSession springBootVersionSessionCallback() { - BeforeSendSession beforeSendSession = new BeforeSendSession() { - @Override - public void beforeSendSession(SessionPayload payload) { - addSpringRuntimeVersion(payload.getDevice()); - } - }; - bugsnag.addBeforeSendSession(beforeSendSession); - return beforeSendSession; - } - - private void addSpringRuntimeVersion(Map device) { - Diagnostics.addDeviceRuntimeVersion(device, "springBoot", SpringBootVersion.getVersion()); - } - - /** - * 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(SpringWebJakartaMvcLoadedCondition.class) - ServletListenerRegistrationBean listenerRegistrationBean() { - ServletListenerRegistrationBean srb = - new ServletListenerRegistrationBean(); - srb.setListener(new BugsnagServletRequestListener()); - return srb; - } -} diff --git a/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java b/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java index db844e14..35c1c759 100644 --- a/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java +++ b/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java @@ -42,7 +42,7 @@ /** * Test that a Spring Boot application configured with the - * {@link com.bugsnag.BugsnagSpringConfiguration} performs as expected. + * {@link BugsnagSpringJakartaConfiguration} performs as expected. */ @RunWith(SpringRunner.class) @SpringBootTest( diff --git a/bugsnag-spring/src/test/java/com/bugsnag/SpringScheduledTaskTest.java b/bugsnag-spring/src/test/java/com/bugsnag/SpringScheduledTaskTest.java index 944b95e9..56806178 100644 --- a/bugsnag-spring/src/test/java/com/bugsnag/SpringScheduledTaskTest.java +++ b/bugsnag-spring/src/test/java/com/bugsnag/SpringScheduledTaskTest.java @@ -29,7 +29,7 @@ /** * Test that a Spring Boot application configured with the - * {@link BugsnagSpringConfiguration} performs as expected. + * {@link BugsnagSpringJakartaConfiguration} performs as expected. */ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestSpringBootApplication.class) From 7d1c9aa4be0d5721fe41741a1e568877350e373a Mon Sep 17 00:00:00 2001 From: David Petran Date: Thu, 30 Mar 2023 10:46:33 +0100 Subject: [PATCH 20/35] attempt to fix pipeline --- .buildkite/pipeline.yml | 2 +- dockerfiles/Dockerfile.java8-mazerunner | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 5b906dd9..7dc9a22c 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -11,7 +11,7 @@ steps: plugins: - docker-compose#v3.7.0: run: java-common - command: './gradlew :bugsnag:assemble :bugsnag-spring:assemble' + command: './gradlew :bugsnag:assemble :bugsnag-spring:javax:assemble :bugsnag-spring:assemble' - label: ':docker: Run JVM tests' key: 'java-jvm-tests' diff --git a/dockerfiles/Dockerfile.java8-mazerunner b/dockerfiles/Dockerfile.java8-mazerunner index 55d8bb29..7bee52f2 100644 --- a/dockerfiles/Dockerfile.java8-mazerunner +++ b/dockerfiles/Dockerfile.java8-mazerunner @@ -1,4 +1,4 @@ -FROM eclipse-temurin:17 as builder +FROM openjdk:17-jdk-slim as builder WORKDIR /app From 99fa977cdbbc46da6a41738d38d7d387663a7ff8 Mon Sep 17 00:00:00 2001 From: David Petran Date: Thu, 30 Mar 2023 11:01:27 +0100 Subject: [PATCH 21/35] fix for war location --- features/scripts/build-plain-spring-app.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/scripts/build-plain-spring-app.sh b/features/scripts/build-plain-spring-app.sh index d74c0422..12c65cd6 100755 --- a/features/scripts/build-plain-spring-app.sh +++ b/features/scripts/build-plain-spring-app.sh @@ -7,5 +7,5 @@ if [[ "${JAVA_VERSION}" == "8"* ]]; then cp features/fixtures/mazerunnerplainspring/build/libs/mazerunnerplainspring.war $CATALINA_HOME/webapps/ROOT.war else ./gradlew -p features/fixtures/mazerunnerplainspring6 war - cp features/fixtures/mazerunnerplainspring/build/libs/mazerunnerplainspring.war $CATALINA_HOME/webapps/ROOT.war + cp features/fixtures/mazerunnerplainspring6/build/libs/mazerunnerplainspring.war $CATALINA_HOME/webapps/ROOT.war fi \ No newline at end of file From bb5b050452941b6b77ea263c91c81549eeee0f9b Mon Sep 17 00:00:00 2001 From: David Petran Date: Thu, 30 Mar 2023 15:22:35 +0100 Subject: [PATCH 22/35] use tomcat v10 (minimum for spring 6) --- dockerfiles/Dockerfile.java17-mazerunner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockerfiles/Dockerfile.java17-mazerunner b/dockerfiles/Dockerfile.java17-mazerunner index 89a134d6..333fbe08 100644 --- a/dockerfiles/Dockerfile.java17-mazerunner +++ b/dockerfiles/Dockerfile.java17-mazerunner @@ -1,4 +1,4 @@ -FROM tomcat:9.0.56-jdk17-temurin +FROM tomcat:10.1-jdk17 WORKDIR /app RUN apt-get update && DEBIAN_FRONTEND=noninteractive \ From 58bbf05f61b13d3ea8ef9261cd110b26486a1563 Mon Sep 17 00:00:00 2001 From: David Petran Date: Thu, 30 Mar 2023 15:36:05 +0100 Subject: [PATCH 23/35] tidy up unneeded loaded condition --- .../com/bugsnag/JavaxMvcConfiguration.java | 2 +- .../bugsnag/SpringBootJavaxConfiguration.java | 2 +- .../SpringWebJakartaMvcLoadedCondition.java | 21 ------------------- ....java => SpringWebMvcLoadedCondition.java} | 5 +---- .../com/bugsnag/JakartaMvcConfiguration.java | 2 +- .../SpringBootJakartaConfiguration.java | 2 +- 6 files changed, 5 insertions(+), 29 deletions(-) delete mode 100644 bugsnag-spring/javax/src/main/java/com/bugsnag/SpringWebJakartaMvcLoadedCondition.java rename bugsnag-spring/javax/src/main/java/com/bugsnag/{SpringWebJavaxMvcLoadedCondition.java => SpringWebMvcLoadedCondition.java} (76%) diff --git a/bugsnag-spring/javax/src/main/java/com/bugsnag/JavaxMvcConfiguration.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/JavaxMvcConfiguration.java index 3660227c..8ba8e5e8 100644 --- a/bugsnag-spring/javax/src/main/java/com/bugsnag/JavaxMvcConfiguration.java +++ b/bugsnag-spring/javax/src/main/java/com/bugsnag/JavaxMvcConfiguration.java @@ -10,7 +10,7 @@ * If spring-webmvc is loaded, add configuration for reporting unhandled exceptions. */ @Configuration -@Conditional(SpringWebJavaxMvcLoadedCondition.class) +@Conditional(SpringWebMvcLoadedCondition.class) class JavaxMvcConfiguration implements InitializingBean { @Autowired diff --git a/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootJavaxConfiguration.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootJavaxConfiguration.java index 6183aafa..b7ae8183 100644 --- a/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootJavaxConfiguration.java +++ b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootJavaxConfiguration.java @@ -23,7 +23,7 @@ class SpringBootJavaxConfiguration extends SpringBootConfiguration { * automatic servlet request metadata collection. */ @Bean - @Conditional(SpringWebJavaxMvcLoadedCondition.class) + @Conditional(SpringWebMvcLoadedCondition.class) ServletListenerRegistrationBean listenerRegistrationBean() { ServletListenerRegistrationBean srb = new ServletListenerRegistrationBean(); diff --git a/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringWebJakartaMvcLoadedCondition.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringWebJakartaMvcLoadedCondition.java deleted file mode 100644 index 7052a899..00000000 --- a/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringWebJakartaMvcLoadedCondition.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.bugsnag; - -import com.bugsnag.callbacks.JakartaServletCallback; - -import org.springframework.context.annotation.Condition; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.type.AnnotatedTypeMetadata; - -/** - * Check whether spring-webmvc is available to the application. - */ -class SpringWebJakartaMvcLoadedCondition implements Condition { - - @Override - public boolean matches(ConditionContext context, - AnnotatedTypeMetadata metadata) { - return context.getClassLoader() != null - && JakartaServletCallback.isAvailable() - && context.getClassLoader().getResource("org/springframework/web/servlet") != null; - } -} diff --git a/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringWebJavaxMvcLoadedCondition.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringWebMvcLoadedCondition.java similarity index 76% rename from bugsnag-spring/javax/src/main/java/com/bugsnag/SpringWebJavaxMvcLoadedCondition.java rename to bugsnag-spring/javax/src/main/java/com/bugsnag/SpringWebMvcLoadedCondition.java index 1709dc4d..f0152181 100644 --- a/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringWebJavaxMvcLoadedCondition.java +++ b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringWebMvcLoadedCondition.java @@ -1,7 +1,5 @@ package com.bugsnag; -import com.bugsnag.callbacks.JavaxServletCallback; - import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; @@ -9,13 +7,12 @@ /** * Check whether spring-webmvc is available to the application. */ -class SpringWebJavaxMvcLoadedCondition implements Condition { +class SpringWebMvcLoadedCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return context.getClassLoader() != null - && JavaxServletCallback.isAvailable() && context.getClassLoader().getResource("org/springframework/web/servlet") != null; } } diff --git a/bugsnag-spring/src/main/java/com/bugsnag/JakartaMvcConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/JakartaMvcConfiguration.java index d3385aa6..2f5b7832 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/JakartaMvcConfiguration.java +++ b/bugsnag-spring/src/main/java/com/bugsnag/JakartaMvcConfiguration.java @@ -10,7 +10,7 @@ * If spring-webmvc is loaded, add configuration for reporting unhandled exceptions. */ @Configuration -@Conditional(SpringWebJakartaMvcLoadedCondition.class) +@Conditional(SpringWebMvcLoadedCondition.class) class JakartaMvcConfiguration implements InitializingBean { @Autowired diff --git a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootJakartaConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootJakartaConfiguration.java index ff0396ee..c9f0d413 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootJakartaConfiguration.java +++ b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootJakartaConfiguration.java @@ -22,7 +22,7 @@ class SpringBootJakartaConfiguration extends SpringBootConfiguration { * automatic servlet request metadata collection. */ @Bean - @Conditional(SpringWebJakartaMvcLoadedCondition.class) + @Conditional(SpringWebMvcLoadedCondition.class) ServletListenerRegistrationBean listenerRegistrationBean() { ServletListenerRegistrationBean srb = new ServletListenerRegistrationBean(); From a8d72f13f4e43e8f2d87f47c8d24f6e6008eb59b Mon Sep 17 00:00:00 2001 From: David Petran Date: Thu, 30 Mar 2023 15:42:13 +0100 Subject: [PATCH 24/35] try different tomcat 10 container --- dockerfiles/Dockerfile.java17-mazerunner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockerfiles/Dockerfile.java17-mazerunner b/dockerfiles/Dockerfile.java17-mazerunner index 333fbe08..f2ba4879 100644 --- a/dockerfiles/Dockerfile.java17-mazerunner +++ b/dockerfiles/Dockerfile.java17-mazerunner @@ -1,4 +1,4 @@ -FROM tomcat:10.1-jdk17 +FROM tomcat:10-jdk17 WORKDIR /app RUN apt-get update && DEBIAN_FRONTEND=noninteractive \ From d1b2dd07994bf0000fcca9955103c9794b0b3b62 Mon Sep 17 00:00:00 2001 From: David Petran Date: Thu, 30 Mar 2023 15:48:49 +0100 Subject: [PATCH 25/35] try different tomcat 10 container --- dockerfiles/Dockerfile.java17-mazerunner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockerfiles/Dockerfile.java17-mazerunner b/dockerfiles/Dockerfile.java17-mazerunner index f2ba4879..14a67456 100644 --- a/dockerfiles/Dockerfile.java17-mazerunner +++ b/dockerfiles/Dockerfile.java17-mazerunner @@ -1,4 +1,4 @@ -FROM tomcat:10-jdk17 +FROM tomcat:10.1.7-jdk17-temurin WORKDIR /app RUN apt-get update && DEBIAN_FRONTEND=noninteractive \ From 8d04c9576dd388e127dab4ce45d00c60efc4100d Mon Sep 17 00:00:00 2001 From: David Petran Date: Thu, 30 Mar 2023 16:15:02 +0100 Subject: [PATCH 26/35] revert unintended changes --- .../src/main/java/com/bugsnag/BugsnagSpringConfiguration.java | 4 ++++ .../src/main/java/com/bugsnag/SpringBootLoadedCondition.java | 1 - bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java | 2 +- .../src/test/java/com/bugsnag/SpringScheduledTaskTest.java | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java index 83206060..86d503ef 100644 --- a/bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java +++ b/bugsnag-spring/javax/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java @@ -11,9 +11,13 @@ import java.util.Map; +/** + * Configuration to integrate Bugsnag with Spring. + */ @Configuration @Import(BugsnagImportSelector.class) public class BugsnagSpringConfiguration implements InitializingBean { + @Autowired private Bugsnag bugsnag; diff --git a/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootLoadedCondition.java b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootLoadedCondition.java index 4911667c..47611314 100644 --- a/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootLoadedCondition.java +++ b/bugsnag-spring/javax/src/main/java/com/bugsnag/SpringBootLoadedCondition.java @@ -12,7 +12,6 @@ class SpringBootLoadedCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { - return context.getClassLoader() != null && context.getClassLoader().getResource("org/springframework/boot") != null; } diff --git a/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java b/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java index 35c1c759..ed03f19f 100644 --- a/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java +++ b/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java @@ -42,7 +42,7 @@ /** * Test that a Spring Boot application configured with the - * {@link BugsnagSpringJakartaConfiguration} performs as expected. + * {@link BugsnagSpringConfiguration} performs as expected. */ @RunWith(SpringRunner.class) @SpringBootTest( diff --git a/bugsnag-spring/src/test/java/com/bugsnag/SpringScheduledTaskTest.java b/bugsnag-spring/src/test/java/com/bugsnag/SpringScheduledTaskTest.java index 56806178..944b95e9 100644 --- a/bugsnag-spring/src/test/java/com/bugsnag/SpringScheduledTaskTest.java +++ b/bugsnag-spring/src/test/java/com/bugsnag/SpringScheduledTaskTest.java @@ -29,7 +29,7 @@ /** * Test that a Spring Boot application configured with the - * {@link BugsnagSpringJakartaConfiguration} performs as expected. + * {@link BugsnagSpringConfiguration} performs as expected. */ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestSpringBootApplication.class) From 61d2502c84d29cad6fcd565a67e6329c588bd46c Mon Sep 17 00:00:00 2001 From: David Petran Date: Thu, 30 Mar 2023 18:37:56 +0100 Subject: [PATCH 27/35] correct configfor spring 6 app --- features/fixtures/mazerunnerplainspring6/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/features/fixtures/mazerunnerplainspring6/build.gradle b/features/fixtures/mazerunnerplainspring6/build.gradle index cb43c4c4..556be222 100644 --- a/features/fixtures/mazerunnerplainspring6/build.gradle +++ b/features/fixtures/mazerunnerplainspring6/build.gradle @@ -16,6 +16,8 @@ dependencies { implementation("jakarta.servlet:jakarta.servlet-api:5.0.0") implementation("ch.qos.logback:logback-classic:1.2.3") + implementation("com.fasterxml.jackson.core:jackson-annotations:2.9.1") + implementation("com.fasterxml.jackson.core:jackson-databind:2.9.1") implementation("com.bugsnag:bugsnag:9.9.9-test") implementation("com.bugsnag:bugsnag-spring:9.9.9-test") implementation project(":scenarios") From d90fb0601b26ccbd5d70afbe73eab43f363fe957 Mon Sep 17 00:00:00 2001 From: David Petran Date: Fri, 31 Mar 2023 08:17:38 +0100 Subject: [PATCH 28/35] update maze-runner --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 6f2460eb..208e640d 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', tag: 'v7.10.2' +gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', tag: 'v7.24.0' gem 'os' From e99235639053e9e4577b7ecc7a28a5e9bf330df6 Mon Sep 17 00:00:00 2001 From: David Petran Date: Fri, 31 Mar 2023 08:59:09 +0100 Subject: [PATCH 29/35] try non ubuntu container --- dockerfiles/Dockerfile.java17-mazerunner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockerfiles/Dockerfile.java17-mazerunner b/dockerfiles/Dockerfile.java17-mazerunner index 14a67456..c578ca05 100644 --- a/dockerfiles/Dockerfile.java17-mazerunner +++ b/dockerfiles/Dockerfile.java17-mazerunner @@ -1,4 +1,4 @@ -FROM tomcat:10.1.7-jdk17-temurin +FROM tomcat:10-jdk17-openjdk-slim WORKDIR /app RUN apt-get update && DEBIAN_FRONTEND=noninteractive \ From 4a118c1488c9ed7b685526f4c9b7e9993f044eae Mon Sep 17 00:00:00 2001 From: David Petran Date: Fri, 31 Mar 2023 11:06:39 +0100 Subject: [PATCH 30/35] revert this unwanted change --- features/steps/build_steps.rb | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/features/steps/build_steps.rb b/features/steps/build_steps.rb index c1aa2528..90cfe241 100644 --- a/features/steps/build_steps.rb +++ b/features/steps/build_steps.rb @@ -41,30 +41,6 @@ } end -When("I run spring boot 3 {string} with the defaults") do |eventType| - steps %Q{ - And I set environment variable "MAZERUNNER_BASE_URL" to "http://localhost:9339/" - And I set environment variable "BUGSNAG_API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I set environment variable "EVENT_TYPE" to "#{eventType}" - And I run the script "features/scripts/run-java-spring-boot-3-app.sh" synchronously - } -end - -Given("I run the plain spring 6 app") do - steps %Q{ - And I set environment variable "MAZERUNNER_BASE_URL" to "http://localhost:9339/" - And I set environment variable "BUGSNAG_API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I run the script "features/scripts/build-plain-spring-6-app.sh" synchronously - } -end - -When("I run plain Spring 6 {string} with the defaults") do |eventType| - steps %Q{ - And I run the plain spring 6 app - And I navigate to the route "/run-scenario/#{eventType}" on port "8080" - } -end - When(/^I navigate to the route "(.*)" on port "(\d*)"/) do |route, port| steps %Q{ When I open the URL "http://localhost:#{port}#{route}" From 00aa56b2622751c009c72920429270f2d8e9d8e0 Mon Sep 17 00:00:00 2001 From: David Petran Date: Fri, 31 Mar 2023 11:07:48 +0100 Subject: [PATCH 31/35] remove this uneeded settings import --- settings.gradle | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/settings.gradle b/settings.gradle index 2bdcece8..332a86da 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,7 +11,4 @@ include ':examples:simple', // jakarta servlet example requires java 11 compatibility for gretty plugin if (JavaVersion.current().isJava11Compatible()) { include ':examples:servlet-jakarta' -} -include 'bugsnag-spring:javax' -findProject(':bugsnag-spring:javax')?.name = 'javax' - +} \ No newline at end of file From 014747f216f382569028f2cb89fc4f9b350116e7 Mon Sep 17 00:00:00 2001 From: DavidPetran <93917014+DavidPetran@users.noreply.github.com> Date: Fri, 26 May 2023 15:13:24 +0100 Subject: [PATCH 32/35] remove dependency on gradle-nexus-plugin (#190) Co-authored-by: David Petran --- bugsnag-spring/build.gradle | 2 +- build.gradle | 8 +++-- common.gradle | 2 +- release.gradle | 68 +++++++++++++++++-------------------- 4 files changed, 39 insertions(+), 41 deletions(-) diff --git a/bugsnag-spring/build.gradle b/bugsnag-spring/build.gradle index f7272f2e..56374aa2 100644 --- a/bugsnag-spring/build.gradle +++ b/bugsnag-spring/build.gradle @@ -33,7 +33,7 @@ dependencies { evaluationDependsOnChildren() -tasks['jar'].mustRunAfter(project(':bugsnag-spring:javax').tasks['jar']) +tasks['jar'].dependsOn(project(':bugsnag-spring:javax').tasks['jar']) jar { from project.sourceSets.main.allSource diff --git a/build.gradle b/build.gradle index b2d60564..3e9e1ea3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,14 @@ buildscript { repositories { mavenCentral() + maven { + url = uri("https://plugins.gradle.org/m2/") + } } dependencies { if (project.hasProperty('releasing')) { - classpath 'com.bmuschko:gradle-nexus-plugin:2.3.1' - classpath 'io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.11.0' - classpath 'net.researchgate:gradle-release:2.4.0' + classpath 'io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.30.0' + classpath 'net.researchgate:gradle-release:3.0.2' } } } diff --git a/common.gradle b/common.gradle index 347151e1..d69f4398 100644 --- a/common.gradle +++ b/common.gradle @@ -11,7 +11,7 @@ if (JavaVersion.current().isJava8Compatible()) { apply plugin: 'checkstyle' } -if (project.hasProperty('releasing')) { +if (project.hasProperty('releasing') && project.depth <= 1) { apply from: "../release.gradle" } diff --git a/release.gradle b/release.gradle index 7a238ad5..3bd92f3e 100644 --- a/release.gradle +++ b/release.gradle @@ -1,4 +1,3 @@ -apply plugin: 'com.bmuschko.nexus' apply plugin: 'net.researchgate.release' apply plugin: 'maven-publish' @@ -13,41 +12,6 @@ nexusStaging { packageGroup = "com.bugsnag" } -modifyPom { - project { - name = project.findProperty('projectName') - description = project.findProperty('projectDescription') - url 'https://github.com/bugsnag/bugsnag-java' - - scm { - url 'https://github.com/bugsnag/bugsnag-java' - connection 'scm:git:git://github.com/bugsnag/bugsnag-java.git' - developerConnection 'scm:git:ssh://git@github.com/bugsnag/bugsnag-java.git' - } - - licenses { - license { - name 'MIT' - url 'http://opensource.org/licenses/MIT' - distribution 'repo' - } - } - - organization { - name 'Bugsnag' - url 'https://bugsnag.com' - } - - developers { - developer { - id 'loopj' - name 'James Smith' - email 'james@bugsnag.com' - } - } - } -} - task sourceJar(type: Jar) { from sourceSets.main.allJava } @@ -62,6 +26,38 @@ publishing { artifact sourceJar { classifier "sources" } + pom { + name = project.findProperty('projectName') + description = project.findProperty('projectDescription') + url = 'https://github.com/bugsnag/bugsnag-java' + + scm { + url = 'https://github.com/bugsnag/bugsnag-java' + connection = 'scm:git:git://github.com/bugsnag/bugsnag-java.git' + developerConnection = 'scm:git:ssh://git@github.com/bugsnag/bugsnag-java.git' + } + + licenses { + license { + name = 'MIT' + url = 'http://opensource.org/licenses/MIT' + distribution = 'repo' + } + } + + organization { + name = 'Bugsnag' + url = 'https://bugsnag.com' + } + + developers { + developer { + id = 'loopj' + name = 'James Smith' + email = 'james@bugsnag.com' + } + } + } } } } From 8a356c3181b826b74a02373069de7fcacf4163ab Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Tue, 6 Jun 2023 15:52:51 +0100 Subject: [PATCH 33/35] revert version number to current version --- bugsnag/src/main/java/com/bugsnag/Notifier.java | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bugsnag/src/main/java/com/bugsnag/Notifier.java b/bugsnag/src/main/java/com/bugsnag/Notifier.java index dbd0b5b5..e3f3c608 100644 --- a/bugsnag/src/main/java/com/bugsnag/Notifier.java +++ b/bugsnag/src/main/java/com/bugsnag/Notifier.java @@ -5,7 +5,7 @@ class Notifier { private static final String NOTIFIER_NAME = "Bugsnag Java"; - private static final String NOTIFIER_VERSION = "4.0.0-beta"; + private static final String NOTIFIER_VERSION = "3.6.4"; private static final String NOTIFIER_URL = "https://github.com/bugsnag/bugsnag-java"; private String notifierName = NOTIFIER_NAME; diff --git a/gradle.properties b/gradle.properties index 3eaf97e4..24d93332 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=4.0.0-beta +version=3.6.4 group=com.bugsnag # Default properties From 62deb735087c37b592252dc716d145aeab94c024 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Tue, 6 Jun 2023 16:11:46 +0100 Subject: [PATCH 34/35] update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c775499..766627fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## TBD +* Support Spring 6 / Spring Boot 3 + [#191](https://github.com/bugsnag/bugsnag-java/pull/191) + * Bump Jackson from 2.13.3 for critical vulnerability fixes [#184](https://github.com/bugsnag/bugsnag-java/pull/184) From 10e2e170e400dc6fa494a9b131bdf8c633a71114 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Tue, 6 Jun 2023 16:47:25 +0100 Subject: [PATCH 35/35] v3.7.0 --- CHANGELOG.md | 2 +- bugsnag/src/main/java/com/bugsnag/Notifier.java | 2 +- gradle.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 766627fd..d2ca48ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## TBD +## 3.7.0 (2023-06-07) * Support Spring 6 / Spring Boot 3 [#191](https://github.com/bugsnag/bugsnag-java/pull/191) diff --git a/bugsnag/src/main/java/com/bugsnag/Notifier.java b/bugsnag/src/main/java/com/bugsnag/Notifier.java index e3f3c608..6d1c07ef 100644 --- a/bugsnag/src/main/java/com/bugsnag/Notifier.java +++ b/bugsnag/src/main/java/com/bugsnag/Notifier.java @@ -5,7 +5,7 @@ class Notifier { private static final String NOTIFIER_NAME = "Bugsnag Java"; - private static final String NOTIFIER_VERSION = "3.6.4"; + private static final String NOTIFIER_VERSION = "3.7.0"; private static final String NOTIFIER_URL = "https://github.com/bugsnag/bugsnag-java"; private String notifierName = NOTIFIER_NAME; diff --git a/gradle.properties b/gradle.properties index 24d93332..13b63305 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=3.6.4 +version=3.7.0 group=com.bugsnag # Default properties