diff --git a/CHANGELOG.md b/CHANGELOG.md index 29bb6d43..44d6d2b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 3.X.X (TBD) + +* Migrate version information to device.runtimeVersions + [#141](https://github.com/bugsnag/bugsnag-java/pull/141) + ## 3.4.6 (2019-04-16) * Swallow Throwables thrown when configuring bugsnag appender diff --git a/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java index 0e3440c5..36fd5ea6 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java +++ b/bugsnag-spring/src/main/java/com/bugsnag/BugsnagSpringConfiguration.java @@ -8,6 +8,7 @@ import org.springframework.context.annotation.Import; import org.springframework.core.SpringVersion; +import java.util.Map; import javax.annotation.PostConstruct; /** @@ -27,17 +28,33 @@ public class BugsnagSpringConfiguration { * Add a callback to add the version of Spring used by the application */ @Bean - Callback springVersionCallback() { + Callback springVersionErrorCallback() { Callback callback = new Callback() { @Override public void beforeNotify(Report report) { - report.addToTab("device", "springVersion", SpringVersion.getVersion()); + 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(); diff --git a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java index f788f206..e1a68105 100644 --- a/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java +++ b/bugsnag-spring/src/main/java/com/bugsnag/SpringBootConfiguration.java @@ -10,6 +10,7 @@ import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import java.util.Map; import javax.servlet.ServletRequestListener; /** @@ -26,17 +27,33 @@ class SpringBootConfiguration { * Add a callback to add the version of Spring Boot used by the application. */ @Bean - Callback springBootVersionCallback() { + Callback springBootVersionErrorCallback() { Callback callback = new Callback() { @Override public void beforeNotify(Report report) { - report.addToTab("device", "springBootVersion", SpringBootVersion.getVersion()); + 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.BugsnagServletContainerInitializer} does not work for Spring Boot, need to * register the {@link BugsnagServletRequestListener} using a Spring Boot diff --git a/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java b/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java index eb2f0473..694bb98b 100644 --- a/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java +++ b/bugsnag-spring/src/test/java/com/bugsnag/SpringMvcTest.java @@ -157,16 +157,18 @@ public void requestMetadataSetCorrectly() { } @Test + @SuppressWarnings("unchecked") public void springVersionSetCorrectly() { callRuntimeExceptionEndpoint(); Report report = verifyAndGetReport(delivery); // Check that the Spring version is set as expected - @SuppressWarnings(value = "unchecked") Map deviceMetadata = - (Map) report.getMetaData().get("device"); - assertEquals(SpringVersion.getVersion(), deviceMetadata.get("springVersion")); - assertEquals(SpringBootVersion.getVersion(), deviceMetadata.get("springBootVersion")); + Map deviceMetadata = report.getDevice(); + Map runtimeVersions = + (Map) deviceMetadata.get("runtimeVersions"); + assertEquals(SpringVersion.getVersion(), runtimeVersions.get("springFramework")); + assertEquals(SpringBootVersion.getVersion(), runtimeVersions.get("springBoot")); } @Test diff --git a/bugsnag/src/main/java/com/bugsnag/BeforeSendSession.java b/bugsnag/src/main/java/com/bugsnag/BeforeSendSession.java new file mode 100644 index 00000000..dcd6a50d --- /dev/null +++ b/bugsnag/src/main/java/com/bugsnag/BeforeSendSession.java @@ -0,0 +1,5 @@ +package com.bugsnag; + +interface BeforeSendSession { + void beforeSendSession(SessionPayload payload); +} diff --git a/bugsnag/src/main/java/com/bugsnag/Bugsnag.java b/bugsnag/src/main/java/com/bugsnag/Bugsnag.java index b9e64414..81f9553f 100644 --- a/bugsnag/src/main/java/com/bugsnag/Bugsnag.java +++ b/bugsnag/src/main/java/com/bugsnag/Bugsnag.java @@ -665,4 +665,8 @@ public static Set uncaughtExceptionClients() { } return Collections.emptySet(); } + + void addBeforeSendSession(BeforeSendSession beforeSendSession) { + sessionTracker.addBeforeSendSession(beforeSendSession); + } } diff --git a/bugsnag/src/main/java/com/bugsnag/Diagnostics.java b/bugsnag/src/main/java/com/bugsnag/Diagnostics.java index 30082ce4..d6aa3e1a 100644 --- a/bugsnag/src/main/java/com/bugsnag/Diagnostics.java +++ b/bugsnag/src/main/java/com/bugsnag/Diagnostics.java @@ -23,9 +23,17 @@ private Map getDefaultDeviceInfo() { map.put("hostname", DeviceCallback.getHostnameValue()); map.put("osName", System.getProperty("os.name")); map.put("osVersion", System.getProperty("os.version")); + map.put("runtimeVersions", getRuntimeVersions()); return map; } + private Map getRuntimeVersions() { + Map runtimeVersions = new HashMap(); + runtimeVersions.put("javaType", System.getProperty("java.runtime.name")); + runtimeVersions.put("javaVersion", System.getProperty("java.runtime.version")); + return runtimeVersions; + } + private Map getDefaultAppInfo(Configuration configuration) { Map map = new HashMap(); @@ -37,4 +45,18 @@ private Map getDefaultAppInfo(Configuration configuration) { } return map; } + + @SuppressWarnings("unchecked") + static void addDeviceRuntimeVersion(Map device, String key, Object value) { + Object obj = device.get("runtimeVersions"); + Map runtimeVersions; + + if (obj instanceof Map) { + runtimeVersions = (Map) obj; + } else { // fallback to creating a new map if payload was mutated + runtimeVersions = new HashMap(); + device.put("runtimeVersions", runtimeVersions); + } + runtimeVersions.put(key, value); + } } diff --git a/bugsnag/src/main/java/com/bugsnag/SessionTracker.java b/bugsnag/src/main/java/com/bugsnag/SessionTracker.java index b0351c42..d0a50472 100644 --- a/bugsnag/src/main/java/com/bugsnag/SessionTracker.java +++ b/bugsnag/src/main/java/com/bugsnag/SessionTracker.java @@ -21,6 +21,7 @@ class SessionTracker { private final Semaphore flushingRequest = new Semaphore(1); private final AtomicBoolean shuttingDown = new AtomicBoolean(); + private final Collection sessionCallbacks = new ConcurrentLinkedQueue(); SessionTracker(Configuration configuration) { this.config = configuration; @@ -88,6 +89,11 @@ private void sendSessions(Date now) { Collection requestValues = new ArrayList(enqueuedSessionCounts); SessionPayload payload = new SessionPayload(requestValues, config); + + for (BeforeSendSession callback : sessionCallbacks) { + callback.beforeSendSession(payload); + } + Delivery delivery = config.sessionDelivery; delivery.deliver(config.serializer, payload, config.getSessionApiHeaders()); enqueuedSessionCounts.removeAll(requestValues); @@ -102,4 +108,8 @@ void shutdown() { sendSessions(new Date(Long.MAX_VALUE)); // flush all remaining sessions } } + + void addBeforeSendSession(BeforeSendSession beforeSendSession) { + sessionCallbacks.add(beforeSendSession); + } } diff --git a/bugsnag/src/main/java/com/bugsnag/callbacks/DeviceCallback.java b/bugsnag/src/main/java/com/bugsnag/callbacks/DeviceCallback.java index b299569e..8ee7e57c 100644 --- a/bugsnag/src/main/java/com/bugsnag/callbacks/DeviceCallback.java +++ b/bugsnag/src/main/java/com/bugsnag/callbacks/DeviceCallback.java @@ -56,8 +56,6 @@ public static void initializeCache() { public void beforeNotify(Report report) { report .addToTab("device", "osArch", System.getProperty("os.arch")) - .addToTab("device", "runtimeName", System.getProperty("java.runtime.name")) - .addToTab("device", "runtimeVersion", System.getProperty("java.runtime.version")) .addToTab("device", "locale", Locale.getDefault()) .setDeviceInfo("hostname", getHostnameValue()) .setDeviceInfo("osName", System.getProperty("os.name")) diff --git a/bugsnag/src/test/java/com/bugsnag/SessionPayloadTest.java b/bugsnag/src/test/java/com/bugsnag/SessionPayloadTest.java index d18ae502..c60c16c1 100644 --- a/bugsnag/src/test/java/com/bugsnag/SessionPayloadTest.java +++ b/bugsnag/src/test/java/com/bugsnag/SessionPayloadTest.java @@ -63,7 +63,7 @@ public void testJsonSerialisation() { JsonNode device = rootNode.get("device"); assertNotNull(device); - assertEquals(3, device.size()); + assertEquals(4, device.size()); } } diff --git a/features/meta_data.feature b/features/meta_data.feature index 9f911388..0a81e7fa 100644 --- a/features/meta_data.feature +++ b/features/meta_data.feature @@ -10,8 +10,6 @@ Scenario: Sends a handled exception which includes custom metadata added in a no When I run spring boot "MetaDataScenario" with the defaults Then I should receive a request And the request is a valid for the error reporting API - And the event "metaData.device.springVersion" is not null - And the event "metaData.device.springBootVersion" is not null And the event "metaData.Custom.foo" equals "Hello World!" Scenario: Sends a handled exception which includes custom metadata added in a notify callback for plain Spring app @@ -19,8 +17,6 @@ Scenario: Sends a handled exception which includes custom metadata added in a no Then I should receive a request And the request is a valid for the error reporting API And the event "metaData.Custom.foo" equals "Hello World!" - And the event "metaData.device.springVersion" is not null - And the event "metaData.device.springBootVersion" is null Scenario: Test logback appender with meta data in the config file When I run "LogbackScenario" with logback config "meta_data_config.xml" diff --git a/features/runtime_versions.feature b/features/runtime_versions.feature new file mode 100644 index 00000000..c40afda2 --- /dev/null +++ b/features/runtime_versions.feature @@ -0,0 +1,53 @@ +Feature: Runtime versions are included in all requests + +### Errors + +Scenario: Runtime versions included in Plain Java error + When I run "HandledExceptionScenario" with the defaults + Then I should receive a request + And the request is valid for the error reporting API + And the payload field "events.0.device.runtimeVersions.javaType" ends with "Runtime Environment" + And the payload field "events.0.device.runtimeVersions.javaVersion" matches the regex "(\d.)+" + +Scenario: Runtime versions included in Spring Framework error + When I run plain Spring "HandledExceptionScenario" with the defaults + Then I should receive a request + And the request is valid for the error reporting API + And the payload field "events.0.device.runtimeVersions.javaType" ends with "Runtime Environment" + And the payload field "events.0.device.runtimeVersions.javaVersion" matches the regex "(\d.)+" + And the payload field "events.0.device.runtimeVersions.springFramework" matches the regex "(\d.)+" + +Scenario: Runtime versions included in Spring Boot error + When I run spring boot "HandledExceptionScenario" with the defaults + Then I should receive a request + And the request is valid for the error reporting API + And the payload field "events.0.device.runtimeVersions.javaType" ends with "Runtime Environment" + And the payload field "events.0.device.runtimeVersions.javaVersion" matches the regex "(\d.)+" + And the payload field "events.0.device.runtimeVersions.springFramework" matches the regex "(\d.)+" + And the payload field "events.0.device.runtimeVersions.springBoot" matches the regex "(\d.)+" + +### Sessions + +Scenario: Runtime versions included in Plain Java session + When I run "ManualSessionScenario" with the defaults + Then I should receive a request + And the request is valid for the session tracking API + And the payload field "device.runtimeVersions.javaType" ends with "Runtime Environment" + And the payload field "device.runtimeVersions.javaVersion" matches the regex "(\d.)+" + +Scenario: Runtime versions included in Spring Framework session + When I run plain Spring "ManualSessionScenario" with the defaults + Then I should receive a request + And the request is valid for the session tracking API + And the payload field "device.runtimeVersions.javaType" ends with "Runtime Environment" + And the payload field "device.runtimeVersions.javaVersion" matches the regex "(\d.)+" + And the payload field "device.runtimeVersions.springFramework" matches the regex "(\d.)+" + +Scenario: Runtime versions included in Spring Boot session + When I run spring boot "ManualSessionScenario" with the defaults + Then I should receive a request + And the request is valid for the session tracking API + And the payload field "device.runtimeVersions.javaType" ends with "Runtime Environment" + And the payload field "device.runtimeVersions.javaVersion" matches the regex "(\d.)+" + And the payload field "device.runtimeVersions.springFramework" matches the regex "(\d.)+" + And the payload field "device.runtimeVersions.springBoot" matches the regex "(\d.)+"