From 0d31a8fd21207bc93505947a8d2d8a2c475786a8 Mon Sep 17 00:00:00 2001 From: Kate Anderson Date: Fri, 10 Apr 2026 13:20:54 -0700 Subject: [PATCH 1/5] Add dropwizard 3 module and update gradle version --- build.gradle.kts | 2 +- dropwizard/build.gradle.kts | 2 +- dropwizard3/README.md | 100 ++++++ dropwizard3/build.gradle.kts | 84 +++++ .../dropwizard3/LogFormatLayoutFactory.java | 40 +++ .../NewRelicAsyncAppenderFactory.java | 19 + .../NewRelicConsoleAppenderFactory.java | 37 ++ .../NewRelicFileAppenderFactory.java | 37 ++ .../NewRelicJsonLayoutFactory.java | 22 ++ .../logging/dropwizard3/access/AccessLog.java | 12 + ...kingMetadataAsRequestAttributesFilter.java | 50 +++ .../access/NewRelicAccessJsonLayout.java | 86 +++++ .../NewRelicAccessJsonLayoutFactory.java | 21 ++ ....dropwizard.logging.common.AppenderFactory | 2 + ...ng.common.layout.DiscoverableLayoutFactory | 3 + .../NewRelicAppenderFactoryTest.java | 335 ++++++++++++++++++ ...MetadataAsRequestAttributesFilterTest.java | 81 +++++ .../access/NewRelicAccessJsonLayoutTest.java | 161 +++++++++ examples/dropwizard-app/build.gradle.kts | 6 +- examples/dropwizard3-app/build.gradle.kts | 41 +++ .../testapps/dropwizard/AppConfiguration.java | 10 + .../newrelic/testapps/dropwizard/Hello.java | 23 ++ .../newrelic/testapps/dropwizard/Main.java | 115 ++++++ examples/dropwizard3-app/test.yml | 28 ++ examples/jul-app/build.gradle.kts | 4 +- examples/log4j1-app/build.gradle.kts | 4 +- examples/log4j2-app/build.gradle.kts | 4 +- examples/logback-app/build.gradle.kts | 4 +- examples/logback11-app/build.gradle.kts | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- performance/log4j2-perf/build.gradle.kts | 10 +- settings.gradle | 2 + 32 files changed, 1330 insertions(+), 21 deletions(-) create mode 100644 dropwizard3/README.md create mode 100644 dropwizard3/build.gradle.kts create mode 100644 dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/LogFormatLayoutFactory.java create mode 100644 dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicAsyncAppenderFactory.java create mode 100644 dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicConsoleAppenderFactory.java create mode 100644 dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicFileAppenderFactory.java create mode 100644 dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicJsonLayoutFactory.java create mode 100644 dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/AccessLog.java create mode 100644 dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilter.java create mode 100644 dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayout.java create mode 100644 dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutFactory.java create mode 100644 dropwizard3/src/main/resources/META-INF/services/io.dropwizard.logging.common.AppenderFactory create mode 100644 dropwizard3/src/main/resources/META-INF/services/io.dropwizard.logging.common.layout.DiscoverableLayoutFactory create mode 100644 dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/NewRelicAppenderFactoryTest.java create mode 100644 dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilterTest.java create mode 100644 dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutTest.java create mode 100644 examples/dropwizard3-app/build.gradle.kts create mode 100644 examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/AppConfiguration.java create mode 100644 examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Hello.java create mode 100644 examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Main.java create mode 100644 examples/dropwizard3-app/test.yml diff --git a/build.gradle.kts b/build.gradle.kts index 0cc6e4c..5a2db28 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ repositories { subprojects { tasks.withType().all { useJUnitPlatform() - reports.junitXml.isEnabled = true + reports.junitXml.required = true } tasks.withType().all { diff --git a/dropwizard/build.gradle.kts b/dropwizard/build.gradle.kts index 5bcf211..7c4b83a 100644 --- a/dropwizard/build.gradle.kts +++ b/dropwizard/build.gradle.kts @@ -51,7 +51,7 @@ tasks.withType { enabled = true } -configure { +java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } diff --git a/dropwizard3/README.md b/dropwizard3/README.md new file mode 100644 index 0000000..bcc1fee --- /dev/null +++ b/dropwizard3/README.md @@ -0,0 +1,100 @@ +# The New Relic Dropwizard Logging Extension + +This is a lift of the existing Dropwizard Logging Extension module. It adds support for Dropwizard logging >= v3.x. + +The primary difference in this module is that it requires Java 11+ (a transitive requirement of Dropwizard v3.x). + +## Preconditions + +1. Dropwizard v3.x or higher must be configured and working in the application with the Dropwizard appenders and logging factory. +2. You must be using Java 11 or higher (required to run Dropwizard 3+). +3. The New Relic Java agent must be enabled using the `-javaagent` command-line parameter. +4. You must be using at least version 5.6.0 of the Java Agent. + +## Configuring + +There are some changes to your application to use the New Relic Dropwizard Logging Extension. All steps are required. + +**Optional**: [Configuration Options](..%2FREADME.md#configuration-options) for setting max stack size or collecting MDC. + +### 1. Include the dependency in your project. + +Refer to [Maven Central](https://search.maven.org/search?q=g:com.newrelic.logging%20a:dropwizard) for the appropriate snippets. + +### 2. Use the `newrelic-console` or `newrelic-file` appender with a `newrelic-json` layout. + +Update your DW configuration yaml like the example below. Modify the appender you have chosen to receive decorated logs. + +If you were using `type: console`, then replace that with `type: newrelic-console`. This is a frequent use case for container-based applications. All +[configuration elements for `type: console`](https://dropwizard.readthedocs.io/en/release-1.3.x/manual/configuration.html#console) +will still apply. + +If you were using `type: file` then replace that with `type: newrelic-file`. All +[configuration elements for `type: file`](https://dropwizard.readthedocs.io/en/release-1.3.x/manual/configuration.html#file) +will still apply. + +```yaml +logging: + appenders: + - type: newrelic-file + # Add the two lines below if you don't have a layout specified on the appender. + # If you do have a layout, remove all parameters to the layout and set the type to newrelic-json. + layout: + type: newrelic-json +``` + +*Why?* The appenders are different because they must capture New Relic-specific data on the thread the log message +is coming from. The `newrelic-file` and `newrelic-console` appenders inherit from the existing `file` and `console` +appenders with a different asynchronous wrapper. Unfortunately, DW service loading does not provide for injecting +that automatically. + +The layout is different because the New Relic log format is a tailored JSON format with specific fields in specific places +that our log forwarder plugins and back end rely on. + +## Fallback layout type + +The New Relic Dropwizard plugin also supports a `log-format` layout type that uses the standard Dropwizard logging. For testing purposes, +you can change the type of the layout with a one-line change. + +```yaml +logging: + appenders: + - type: newrelic-file + # This format will be ignored by the `newrelic-json` layout, but used by the `log-format` layout. + # The `replace` parameter in the PatternLayout retrieves all MDC values and strips off the + # `NewRelic:` prefix from all linking metadata keys. + logFormat: "%date{ISO8601} %c %-5p: %m %replace(%mdc{}){'NewRelic:', ''}%n" + layout: +# type: newrelic-json + type: log-format +``` + +## Adding linking metadata to the request log + +There are two important steps for adding linking metadata to the request log. + +### Include the `LinkingMetadataAsRequestAttributesFilter` for all resources + +```java + @Override + public void run(AppConfiguration configuration, Environment environment) { + environment.servlets().addFilter("trace-into-request", new LinkingMetadataAsRequestAttributesFilter()) + .addMappingForUrlPatterns(null, true, "/*"); + } +``` + +### Configure your request log to use `newrelic-access-json` + +This layout type is a JSON format stylized after OpenTelemetry attributes. _Do not_ use a `newrelic-` appender, only use the `newrelic-access-json` layout. + +```yaml +server: + requestLog: + appenders: + - type: console + layout: + type: newrelic-access-json +``` + +-------------- +Dropwizard is © Copyright 2010-2013, Coda Hale, Yammer Inc., 2014-2017 Dropwizard Team. diff --git a/dropwizard3/build.gradle.kts b/dropwizard3/build.gradle.kts new file mode 100644 index 0000000..c149033 --- /dev/null +++ b/dropwizard3/build.gradle.kts @@ -0,0 +1,84 @@ +plugins { + java + id("com.github.spotbugs").version("4.4.4") +} + +group = "com.newrelic.logging" + +// -Prelease=true will render a non-snapshot version +// All other values (including unset) will render a snapshot version. +val release: String? by project +val releaseVersion: String by project +version = releaseVersion + if ("true" == release) "" else "-SNAPSHOT" + +repositories { + mavenCentral() + maven(url = "https://dl.bintray.com/mockito/maven/") +} + +val includeInJar: Configuration by configurations.creating +includeInJar.exclude(group = "org.apache.commons") +configurations["compileOnly"].extendsFrom(includeInJar) + +dependencies { + implementation("io.dropwizard:dropwizard-logging:3.0.0") + implementation("com.github.ben-manes.caffeine:caffeine:3.1.5") + implementation("io.dropwizard:dropwizard-request-logging:3.0.0") + implementation("javax.servlet:javax.servlet-api:3.1.0") + + implementation("com.newrelic.agent.java:newrelic-api:9.1.0") + includeInJar(project(":logback")) { + isTransitive = false + } + + testImplementation("org.junit.jupiter:junit-jupiter:5.6.2") + testImplementation("org.mockito:mockito-core:3.4.4") + testImplementation("org.mockito:mockito-junit-jupiter:3.4.4") + testImplementation("org.hamcrest:hamcrest:2.2") + testImplementation(project(":logback")) + testImplementation(project(":core-test")) +} + +val jar by tasks.getting(Jar::class) { + from(configurations["includeInJar"].flatMap { + when { + it.isDirectory -> listOf(it) + else -> listOf(zipTree(it)) + } + }) +} + +tasks.withType { + enabled = true +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +tasks.withType().configureEach { + options.release.set(11) +} + + +tasks.register("sourcesJar") { + from(sourceSets.main.get().allJava) + archiveClassifier.set("sources") +} + +tasks.register("javadocJar") { + from(tasks.javadoc) + archiveClassifier.set("javadoc") +} + +apply(from = "$rootDir/gradle/publish.gradle.kts") + +tasks.withType { + reports.create("html") { + isEnabled = true + } +} \ No newline at end of file diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/LogFormatLayoutFactory.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/LogFormatLayoutFactory.java new file mode 100644 index 0000000..ac39f16 --- /dev/null +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/LogFormatLayoutFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019. New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.newrelic.logging.dropwizard3; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.LayoutBase; +import com.fasterxml.jackson.annotation.JsonTypeName; +import io.dropwizard.logging.common.DropwizardLayout; +import io.dropwizard.logging.common.layout.DiscoverableLayoutFactory; + +import java.util.TimeZone; + +/** + * The purpose of this layout factory is to provide a fallback so that + * users only need to change the layout `type` element (between {@literal newrelic-json} and + * {@literal log-format}) to switch between json and formatted output. + *

+ * The other option is to fully remove {@literal layout} from the YAML. + */ +@JsonTypeName("log-format") +public class LogFormatLayoutFactory implements DiscoverableLayoutFactory { + private String logFormat; + + void setLogFormat(String logFormat) { + this.logFormat = logFormat; + } + + @Override + public LayoutBase build(LoggerContext context, TimeZone timeZone) { + DropwizardLayout layout = new DropwizardLayout(context, timeZone); + if (logFormat != null && !logFormat.isEmpty()) { + layout.setPattern(logFormat); + } + return layout; + } +} diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicAsyncAppenderFactory.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicAsyncAppenderFactory.java new file mode 100644 index 0000000..319e7bf --- /dev/null +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicAsyncAppenderFactory.java @@ -0,0 +1,19 @@ +/* + * Copyright 2019 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.newrelic.logging.dropwizard3; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AsyncAppenderBase; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.newrelic.logging.logback.NewRelicAsyncAppender; +import io.dropwizard.logging.common.async.AsyncLoggingEventAppenderFactory; + +@JsonTypeName("newrelic") +public class NewRelicAsyncAppenderFactory extends AsyncLoggingEventAppenderFactory { + @Override + public AsyncAppenderBase build() { + return new NewRelicAsyncAppender(); + } +} diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicConsoleAppenderFactory.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicConsoleAppenderFactory.java new file mode 100644 index 0000000..4f8d6e0 --- /dev/null +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicConsoleAppenderFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.newrelic.logging.dropwizard3; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.Context; +import ch.qos.logback.core.LayoutBase; +import com.fasterxml.jackson.annotation.JsonTypeName; +import io.dropwizard.logging.common.ConsoleAppenderFactory; +import io.dropwizard.logging.common.async.AsyncAppenderFactory; +import io.dropwizard.logging.common.layout.LayoutFactory; + +@JsonTypeName("newrelic-console") +public class NewRelicConsoleAppenderFactory extends ConsoleAppenderFactory { + @Override + protected Appender wrapAsync(Appender appender, AsyncAppenderFactory asyncAppenderFactory) { + return super.wrapAsync(appender, new NewRelicAsyncAppenderFactory()); + } + + @Override + protected Appender wrapAsync(Appender appender, AsyncAppenderFactory asyncAppenderFactory, Context context) { + return super.wrapAsync(appender, new NewRelicAsyncAppenderFactory(), context); + } + + @Override + protected LayoutBase buildLayout(LoggerContext context, LayoutFactory defaultLayoutFactory) { + if (layout instanceof LogFormatLayoutFactory) { + ((LogFormatLayoutFactory)layout).setLogFormat(logFormat); + } + + return super.buildLayout(context, defaultLayoutFactory); + } +} diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicFileAppenderFactory.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicFileAppenderFactory.java new file mode 100644 index 0000000..33dad89 --- /dev/null +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicFileAppenderFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.newrelic.logging.dropwizard3; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.Context; +import ch.qos.logback.core.LayoutBase; +import com.fasterxml.jackson.annotation.JsonTypeName; +import io.dropwizard.logging.common.FileAppenderFactory; +import io.dropwizard.logging.common.async.AsyncAppenderFactory; +import io.dropwizard.logging.common.layout.LayoutFactory; + +@JsonTypeName("newrelic-file") +public class NewRelicFileAppenderFactory extends FileAppenderFactory { + @Override + protected Appender wrapAsync(Appender appender, AsyncAppenderFactory asyncAppenderFactory) { + return super.wrapAsync(appender, new NewRelicAsyncAppenderFactory()); + } + + @Override + protected Appender wrapAsync(Appender appender, AsyncAppenderFactory asyncAppenderFactory, Context context) { + return super.wrapAsync(appender, new NewRelicAsyncAppenderFactory(), context); + } + + @Override + protected LayoutBase buildLayout(LoggerContext context, LayoutFactory defaultLayoutFactory) { + if (layout instanceof LogFormatLayoutFactory) { + ((LogFormatLayoutFactory)layout).setLogFormat(logFormat); + } + + return super.buildLayout(context, defaultLayoutFactory); + } +} diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicJsonLayoutFactory.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicJsonLayoutFactory.java new file mode 100644 index 0000000..7269123 --- /dev/null +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicJsonLayoutFactory.java @@ -0,0 +1,22 @@ +/* + * Copyright 2019 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.newrelic.logging.dropwizard3; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.LayoutBase; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.newrelic.logging.logback.NewRelicJsonLayout; +import io.dropwizard.logging.common.layout.DiscoverableLayoutFactory; + +import java.util.TimeZone; + +@JsonTypeName("newrelic-json") +public class NewRelicJsonLayoutFactory implements DiscoverableLayoutFactory { + @Override + public LayoutBase build(LoggerContext context, TimeZone timeZone) { + return new NewRelicJsonLayout(); + } +} diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/AccessLog.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/AccessLog.java new file mode 100644 index 0000000..e8cea2b --- /dev/null +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/AccessLog.java @@ -0,0 +1,12 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.newrelic.logging.dropwizard3.access; + +public class AccessLog { + public static final String LINKING_NAMESPACE = "com.newrelic.linking."; + + private AccessLog() { + } +} diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilter.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilter.java new file mode 100644 index 0000000..e7a6fa6 --- /dev/null +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilter.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.newrelic.logging.dropwizard3.access; + +import com.newrelic.api.agent.Agent; +import com.newrelic.api.agent.NewRelic; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import java.io.IOException; +import java.util.function.Supplier; + +/** + * This filter adds New Relic's linking metadata to the {@link ServletRequest} attributes. + * + * @see Agent#getLinkingMetadata() + * @see ServletRequest#setAttribute(String, Object) + */ +public class LinkingMetadataAsRequestAttributesFilter implements javax.servlet.Filter { + public LinkingMetadataAsRequestAttributesFilter() { + this(NewRelic::getAgent); + } + + LinkingMetadataAsRequestAttributesFilter(Supplier agentSupplier) { + this.agentSupplier = agentSupplier; + } + + @Override + public void init(FilterConfig filterConfig) { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + chain.doFilter(request, response); + + agentSupplier.get().getLinkingMetadata() + .forEach((key, value) -> request.setAttribute(AccessLog.LINKING_NAMESPACE + key, value)); + } + + @Override + public void destroy() { + } + + private final Supplier agentSupplier; +} diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayout.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayout.java new file mode 100644 index 0000000..9734f9e --- /dev/null +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayout.java @@ -0,0 +1,86 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.newrelic.logging.dropwizard3.access; + +import ch.qos.logback.access.spi.IAccessEvent; +import ch.qos.logback.core.LayoutBase; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Arrays; + +/** + * Provides a representation of the {@link IAccessEvent} in JSON. The attribute names are sourced from + * OpenTelemetry. + */ +public class NewRelicAccessJsonLayout extends LayoutBase { + @Override + public String doLayout(IAccessEvent event) { + StringWriter sw = new StringWriter(); + + try (JsonGenerator generator = new JsonFactory().createGenerator(sw)) { + writeToGenerator(event, generator); + } catch (Throwable throwable) { + sw.append(throwable.getMessage()); + } + + sw.append('\n'); + return sw.toString(); + } + + private void writeToGenerator(IAccessEvent event, JsonGenerator generator) throws IOException { + generator.writeStartObject(); + + generator.writeNumberField("timestamp", event.getTimeStamp()); + generator.writeNumberField("duration", event.getElapsedTime()); + + writeHttpTarget(event, generator); + writeHttpFlavor(event, generator); + + generator.writeNumberField("http.status_code", event.getStatusCode()); + generator.writeStringField("http.method", event.getMethod()); + generator.writeStringField("http.user_agent", event.getRequestHeader("User-Agent")); + generator.writeNumberField("http.response_content_length", event.getContentLength()); + generator.writeStringField("net.peer.ip", event.getRemoteAddr()); + generator.writeStringField("net.peer.host", event.getRemoteHost()); + + // Dropwizard logging (via underlying logback-access library) doesn't + // support MDC on access request logs thus we only add NR linking metadata + writeLinkingMetadata(event, generator); + generator.writeEndObject(); + } + + private void writeLinkingMetadata(IAccessEvent event, JsonGenerator generator) throws IOException { + for (String key : Arrays.asList("entity.guid", "entity.name", "entity.type", "hostname", "trace.id")) { + String value = event.getAttribute(AccessLog.LINKING_NAMESPACE + key); + if (value != null && !value.equals("") && !value.equals("-")) { + generator.writeStringField(key, value); + } + } + } + + private void writeHttpFlavor(IAccessEvent event, JsonGenerator generator) throws IOException { + String protocol = event.getProtocol(); + if (protocol != null) { + String[] protocolPieces = protocol.split("/"); + if (protocolPieces.length > 1) { + generator.writeStringField("http.flavor", protocolPieces[1]); + } + } + } + + private void writeHttpTarget(IAccessEvent event, JsonGenerator generator) throws IOException { + String target = ""; + if (event.getRequestURI() != null && !event.getRequestURI().equals("")) { + target += event.getRequestURI(); + } + if (event.getQueryString() != null && !event.getQueryString().equals("")) { + target += event.getQueryString(); + } + generator.writeStringField("http.target", target); + } +} diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutFactory.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutFactory.java new file mode 100644 index 0000000..fa984bc --- /dev/null +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutFactory.java @@ -0,0 +1,21 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.newrelic.logging.dropwizard3.access; + +import ch.qos.logback.access.spi.IAccessEvent; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.core.LayoutBase; +import com.fasterxml.jackson.annotation.JsonTypeName; +import io.dropwizard.logging.common.layout.DiscoverableLayoutFactory; + +import java.util.TimeZone; + +@JsonTypeName("newrelic-access-json") +public class NewRelicAccessJsonLayoutFactory implements DiscoverableLayoutFactory { + @Override + public LayoutBase build(LoggerContext context, TimeZone timeZone) { + return new NewRelicAccessJsonLayout(); + } +} diff --git a/dropwizard3/src/main/resources/META-INF/services/io.dropwizard.logging.common.AppenderFactory b/dropwizard3/src/main/resources/META-INF/services/io.dropwizard.logging.common.AppenderFactory new file mode 100644 index 0000000..82344de --- /dev/null +++ b/dropwizard3/src/main/resources/META-INF/services/io.dropwizard.logging.common.AppenderFactory @@ -0,0 +1,2 @@ +com.newrelic.logging.dropwizard3.NewRelicFileAppenderFactory +com.newrelic.logging.dropwizard3.NewRelicConsoleAppenderFactory diff --git a/dropwizard3/src/main/resources/META-INF/services/io.dropwizard.logging.common.layout.DiscoverableLayoutFactory b/dropwizard3/src/main/resources/META-INF/services/io.dropwizard.logging.common.layout.DiscoverableLayoutFactory new file mode 100644 index 0000000..a3c8cc0 --- /dev/null +++ b/dropwizard3/src/main/resources/META-INF/services/io.dropwizard.logging.common.layout.DiscoverableLayoutFactory @@ -0,0 +1,3 @@ +com.newrelic.logging.dropwizard3.access.NewRelicAccessJsonLayoutFactory +com.newrelic.logging.dropwizard3.NewRelicJsonLayoutFactory +com.newrelic.logging.dropwizard3.LogFormatLayoutFactory diff --git a/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/NewRelicAppenderFactoryTest.java b/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/NewRelicAppenderFactoryTest.java new file mode 100644 index 0000000..e7803d7 --- /dev/null +++ b/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/NewRelicAppenderFactoryTest.java @@ -0,0 +1,335 @@ +/* + * Copyright 2019. New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.newrelic.logging.dropwizard3; + +import ch.qos.logback.classic.AsyncAppender; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.LoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.OutputStreamAppender; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableMap; +import com.newrelic.api.agent.Agent; +import com.newrelic.logging.core.LogAsserts; +import com.newrelic.logging.logback.NewRelicAsyncAppender; +import io.dropwizard.logging.common.AbstractOutputStreamAppenderFactory; +import io.dropwizard.logging.common.async.AsyncLoggingEventAppenderFactory; +import io.dropwizard.logging.common.filter.NullLevelFilterFactory; +import io.dropwizard.logging.common.layout.DropwizardLayoutFactory; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; +import org.slf4j.MDC; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +class NewRelicAppenderFactoryTest { + @SuppressWarnings("WeakerAccess") + @TempDir + Path tempDir; + private AsyncAppender appender; + private LoggingEvent event; + private AbstractOutputStreamAppenderFactory appenderFactory; + private PipedOutputStream outputStream; + private BufferedReader bufferedReader; + private String output; + + @Test + @Timeout(3) + void shouldWrapLogFormatConsoleAppenderCorrectly() throws Throwable { + givenMockAgentData(); + givenOurConsoleAppenderFactory(); + givenOurAppenderFactoryHasLogFormatLayout(); + givenARedirectedAppender(); + givenALoggingEvent(); + whenTheEventIsAppended(); + thenMockAgentDataIsInTheMessage(); + thenLogFormatLayoutWasUsed(); + } + + @Test + @Timeout(3) + void shouldWrapLogFormatFileAppenderCorrectly() throws Throwable { + givenMockAgentData(); + givenOurFileAppenderFactory(); + givenOurAppenderFactoryHasLogFormatLayout(); + givenARedirectedAppender(); + givenALoggingEvent(); + whenTheEventIsAppended(); + thenMockAgentDataIsInTheMessage(); + thenLogFormatLayoutWasUsed(); + } + + @Test + @Timeout(3) + void shouldWrapJsonConsoleAppenderCorrectly() throws Throwable { + givenMockAgentData(); + givenOurConsoleAppenderFactory(); + givenOurAppenderFactoryHasJsonLayoutType(); + givenARedirectedAppender(); + givenALoggingEvent(); + whenTheEventIsAppended(); + thenMockAgentDataIsInTheMessage(); + thenJsonLayoutWasUsed(); + } + + @Test + @Timeout(3) + void shouldWrapJsonFileAppenderCorrectly() throws Throwable { + givenMockAgentData(); + givenOurFileAppenderFactory(); + givenOurAppenderFactoryHasJsonLayoutType(); + givenARedirectedAppender(); + givenALoggingEvent(); + whenTheEventIsAppended(); + thenMockAgentDataIsInTheMessage(); + thenJsonLayoutWasUsed(); + } + + @Test + @Timeout(3) + void shouldAppendCallerDataToJsonCorrectly() throws Throwable { + givenMockAgentData(); + givenOurFileAppenderFactory(); + givenOurAppenderFactoryHasJsonLayoutType(); + givenARedirectedAppender(); + givenALoggingEventWithCallerData(); + whenTheEventIsAppended(); + thenJsonLayoutWasUsed(); + thenTheCallerDataIsInTheMessage(); + } + + @Test + @Timeout(3) + void shouldAppendMDCArgsToJsonWhenEnabled() throws Throwable { + givenMockAgentData(); + givenOurFileAppenderFactory(); + givenOurAppenderFactoryHasJsonLayoutType(); + givenARedirectedAppender(); + givenALoggingEventWithMDCEnabled(); + whenTheEventIsAppended(); + thenJsonLayoutWasUsed(); + thenTheMDCFieldsAreInTheMessage(true); + } + + @Test + @Timeout(3) + void shouldNotAppendMDCArgsToJsonWhenDisabled() throws Throwable { + givenMockAgentData(); + givenOurFileAppenderFactory(); + givenOurAppenderFactoryHasJsonLayoutType(); + givenARedirectedAppender(); + givenALoggingEventWithMDCDisabled(); + whenTheEventIsAppended(); + thenJsonLayoutWasUsed(); + thenTheMDCFieldsAreInTheMessage(false); + } + + private void thenTheMDCFieldsAreInTheMessage(boolean shouldExist) throws Throwable { + String result = getOutput(); + boolean contextKey1Exists = LogAsserts.assertFieldExistence( + "context.contextKey1", + result, + shouldExist + ); + assertEquals(shouldExist, contextKey1Exists, "MDC context.contextKey1 should exist: " + shouldExist); + + boolean contextKey2Exists = LogAsserts.assertFieldExistence( + "context.contextKey2", + result, + shouldExist + ); + assertEquals(shouldExist, contextKey2Exists, "MDC context.contextKey2 should exist: " + shouldExist); + + boolean contextKey3Exists = LogAsserts.assertFieldExistence( + "context.contextKey3", + result, + shouldExist + ); + assertEquals(shouldExist, contextKey3Exists, "MDC context.contextKey3 should exist: " + shouldExist); + } + + private void givenMockAgentData() { + Agent mockAgent = Mockito.mock(Agent.class); + Mockito.when(mockAgent.getLinkingMetadata()).thenReturn(ImmutableMap.of("some.key", "some.value")); + NewRelicAsyncAppender.agentSupplier = () -> mockAgent; + } + + private void givenOurFileAppenderFactory() { + NewRelicFileAppenderFactory factory = new NewRelicFileAppenderFactory(); + + // this output stream will be ignored, but we need it for configuration purposes. + factory.setCurrentLogFilename(tempDir.resolve("log.txt").toString()); + factory.setArchive(false); + appenderFactory = factory; + } + + private void givenOurConsoleAppenderFactory() { + appenderFactory = new NewRelicConsoleAppenderFactory(); + } + + private void givenOurAppenderFactoryHasLogFormatLayout() { + appenderFactory.setLayout(new LogFormatLayoutFactory()); + appenderFactory.setLogFormat("%-5p : %m : %replace(%mdc{}){'NewRelic:', ''}%n"); + } + + private void givenOurAppenderFactoryHasJsonLayoutType() { + appenderFactory.setLayout(new NewRelicJsonLayoutFactory()); + } + + private void givenALoggingEvent() { + event = new LoggingEvent(); + event.setMessage("test_error_message"); + event.setLevel(Level.ERROR); + } + + private void givenALoggingEventWithMDCEnabled() { + // Enable MDC collection + System.setProperty("newrelic.log_extension.add_mdc", "true"); + + // Add MDC data + MDC.put("contextKey1", "contextData1"); + MDC.put("contextKey2", "contextData2"); + MDC.put("contextKey3", "contextData3"); + + givenALoggingEvent(); + } + + private void givenALoggingEventWithMDCDisabled() { + // Enable MDC collection + System.setProperty("newrelic.log_extension.add_mdc", "false"); + + // Add MDC data + MDC.put("contextKey1", "contextData1"); + MDC.put("contextKey2", "contextData2"); + MDC.put("contextKey3", "contextData3"); + + givenALoggingEvent(); + } + + private void givenALoggingEventWithCallerData() { + givenALoggingEvent(); + event.setCallerData(new StackTraceElement[] { new Exception().getStackTrace()[0] }); + } + + private void givenARedirectedAppender() { + LoggerContext context = new LoggerContext(); + + Appender baseAppender = appenderFactory.build( + context, + "app name", + new DropwizardLayoutFactory(), // default, unused in this case + new NullLevelFilterFactory<>(), + new AsyncLoggingEventAppenderFactory() // default, our classes replace this argument. + ); + + assertNotNull(baseAppender); + + if (baseAppender instanceof AsyncAppender) { + appender = ((AsyncAppender) baseAppender); + appender.iteratorForAppenders().forEachRemaining(app -> { + if (app instanceof OutputStreamAppender) { + ((OutputStreamAppender) app).setOutputStream(outputStream); + } + }); + } else { + fail("Expected the appender (" + baseAppender.getClass() + ") to implement NewRelicAsyncAppender."); + } + } + + private void whenTheEventIsAppended() { + appender.doAppend(event); + } + + private void thenLogFormatLayoutWasUsed() throws IOException { + assertEquals("ERROR : test_error_message : some.key=some.value\n", getOutput()); + } + + private void thenJsonLayoutWasUsed() throws IOException { + assertTrue(getOutput().contains("\"message\":\"test_error_message\"")); + assertTrue(getOutput().contains("\"log.level\":\"ERROR\"")); + assertTrue(getOutput().contains("\"some.key\":\"some.value\"")); + + JsonParser parser = new JsonFactory().createParser(getOutput()); + parser.setCodec(new ObjectMapper()); + assertTrue(parser.readValueAsTree() instanceof ObjectNode); + } + + private void thenMockAgentDataIsInTheMessage() throws Throwable { + assertTrue( + getOutput().contains("some.key=some.value") + || getOutput().contains("\"some.key\":\"some.value\""), + "Expected >>" + getOutput() + "<< to contain some.key to some.value" + ); + } + + private void thenTheCallerDataIsInTheMessage() throws Throwable { + JsonParser parser = new JsonFactory().createParser(getOutput()); + parser.setCodec(new ObjectMapper()); + ObjectNode objectNode = parser.readValueAsTree(); + assertEquals(this.getClass().getName(), objectNode.get("class.name").asText()); + assertEquals("givenALoggingEventWithCallerData", objectNode.get("method.name").asText()); + assertTrue(objectNode.get("line.number").isNumber()); + } + + private String getOutput() throws IOException { + if (output == null) { + output = bufferedReader.readLine() + "\n"; + } + return output; + } + + @BeforeEach + void setUp() throws Exception { + // Clear MDC data before each test + MDC.clear(); + outputStream = new PipedOutputStream(); + PipedInputStream inputStream = new PipedInputStream(outputStream); + bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + } + + @AfterEach + void tearDown() throws Exception { + // Clear MDC data after each test + MDC.clear(); + outputStream.close(); + bufferedReader.close(); + } + + @BeforeAll + static void setUpClass() { + savedSupplier = NewRelicAsyncAppender.agentSupplier; + } + + @AfterAll + static void tearDownClass() { + NewRelicAsyncAppender.agentSupplier = savedSupplier; + } + + private static Supplier savedSupplier; +} diff --git a/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilterTest.java b/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilterTest.java new file mode 100644 index 0000000..6df08da --- /dev/null +++ b/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilterTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2019. New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.newrelic.logging.dropwizard3.access; + +import com.google.common.collect.ImmutableMap; +import com.newrelic.api.agent.Agent; +import com.newrelic.api.agent.NewRelic; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import java.io.IOException; +import java.util.function.Supplier; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class LinkingMetadataAsRequestAttributesFilterTest { + + private Supplier agentSupplier = NewRelic::getAgent; + + @Test + void addsAndPrefixesLinkingMetadata() throws IOException, ServletException { + givenMockAgentData(); + whenTheFilterIsInvoked(); + thenTheChainContinued(); + thenTheAttributesWereSet(); + } + + @Test + void worksWithNoAgentData() throws IOException, ServletException { + whenTheFilterIsInvoked(); + thenTheChainContinued(); + thenNoAttributesWereSet(); + } + + private void givenMockAgentData() { + Agent mockAgent = mock(Agent.class); + Mockito.when(mockAgent.getLinkingMetadata()).thenReturn(ImmutableMap.of("some.key", "some.value", "other.key", "other.value")); + agentSupplier = () -> mockAgent; + } + + private void whenTheFilterIsInvoked() throws IOException, ServletException { + LinkingMetadataAsRequestAttributesFilter target = new LinkingMetadataAsRequestAttributesFilter(agentSupplier); + target.doFilter(mockRequest, mock(ServletResponse.class), mockChain); + } + + private void thenTheAttributesWereSet() { + verify(mockRequest, times(1)).setAttribute("com.newrelic.linking.some.key", "some.value"); + verify(mockRequest, times(1)).setAttribute("com.newrelic.linking.other.key", "other.value"); + } + + private void thenNoAttributesWereSet() { + verify(mockRequest, never()).setAttribute(anyString(), anyString()); + } + + private void thenTheChainContinued() throws IOException, ServletException { + verify(mockChain, times(1)).doFilter(eq(mockRequest), any(ServletResponse.class)); + } + + @Mock + ServletRequest mockRequest; + + @Mock + FilterChain mockChain; +} \ No newline at end of file diff --git a/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutTest.java b/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutTest.java new file mode 100644 index 0000000..efd3a2e --- /dev/null +++ b/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutTest.java @@ -0,0 +1,161 @@ +/* + * Copyright 2019. New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.newrelic.logging.dropwizard3.access; + +import ch.qos.logback.access.spi.IAccessEvent; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class NewRelicAccessJsonLayoutTest { + @Test + void generatesDecentData() { + givenMockDataForBasicFields(); + whenMockEventIsSerialized(); + thenStartsAndEndsWithCurlyBraces(); + thenBasicFieldsAreSerialized(); + } + + @Test + void isNullSafe() { + // no mocking, all stubs return null + whenMockEventIsSerialized(); + // the formatter will only output closing curly brace if there are no exceptions. + thenStartsAndEndsWithCurlyBraces(); + } + + @Test + void doesNotSetFlavorWithoutAtLeastOneSlash() { + givenProtocolHasNoSlashes(); + whenMockEventIsSerialized(); + thenStartsAndEndsWithCurlyBraces(); + thenDoesNotContainHttpFlavor(); + } + + @Test + void omitsNullEmptyAndDashLinkingMetadata() { + givenLinkingMetadataWithVariations(); + whenMockEventIsSerialized(); + thenStartsAndEndsWithCurlyBraces(); + thenNullEmptyAndDashFieldsAreNotIncluded(); + thenPopulatedFieldsAreIncluded(); + } + + @ParameterizedTest + @MethodSource("threeStringHttpTarget") + void shouldGenerateTargetStringSafely(String uri, String query, String expected) { + givenMockDataForRequestURIAndQuery(uri, query); + whenMockEventIsSerialized(); + thenHttpTargetShouldBeSafe(expected); + } + + static Stream threeStringHttpTarget() { + return Stream.of( + arguments("r", "q", "rq"), + arguments("r", null, "r"), + arguments("r", "", "r"), + arguments(null, "q", "q"), + arguments("", "q", "q"), + arguments("", "", ""), + arguments(null, "", ""), + arguments("", null, ""), + arguments(null, null, "") + ); + } + + private void givenMockDataForRequestURIAndQuery(String uri, String query) { + when(mockEvent.getRequestURI()).thenReturn(uri); + when(mockEvent.getQueryString()).thenReturn(query); + } + + private void givenLinkingMetadataWithVariations() { + when(mockEvent.getAttribute("com.newrelic.linking.entity.guid")).thenReturn(null); + when(mockEvent.getAttribute("com.newrelic.linking.entity.name")).thenReturn(""); + when(mockEvent.getAttribute("com.newrelic.linking.entity.type")).thenReturn("-"); + when(mockEvent.getAttribute("com.newrelic.linking.trace.id")).thenReturn("some-trace"); + } + + private void givenProtocolHasNoSlashes() { + when(mockEvent.getProtocol()).thenReturn("http"); + } + + private void givenMockDataForBasicFields() { + when(mockEvent.getTimeStamp()).thenReturn(12345L); + when(mockEvent.getElapsedTime()).thenReturn(234L); + when(mockEvent.getProtocol()).thenReturn("HTTP/1.1"); + when(mockEvent.getRequestURI()).thenReturn("/some/path"); + when(mockEvent.getQueryString()).thenReturn("?param=value"); + when(mockEvent.getStatusCode()).thenReturn(202); + when(mockEvent.getMethod()).thenReturn("GET"); + when(mockEvent.getRequestHeader("User-Agent")).thenReturn("curl/7.64.31"); + when(mockEvent.getContentLength()).thenReturn(3234L); + when(mockEvent.getRemoteAddr()).thenReturn("remote-addr"); + when(mockEvent.getRemoteHost()).thenReturn("remote-host"); + } + + private void whenMockEventIsSerialized() { + NewRelicAccessJsonLayout target = new NewRelicAccessJsonLayout(); + result = target.doLayout(mockEvent); + } + + private void thenStartsAndEndsWithCurlyBraces() { + assertThat(result, startsWith("{")); + assertThat(result.trim(), endsWith("}")); + } + + private void thenBasicFieldsAreSerialized() { + assertThat(result, containsString("\"timestamp\":12345")); + assertThat(result, containsString("\"duration\":234")); + assertThat(result, containsString("\"http.flavor\":\"1.1\"")); + assertThat(result, containsString("\"http.target\":\"/some/path?param=value\"")); + assertThat(result, containsString("\"http.status_code\":202")); + assertThat(result, containsString("\"http.user_agent\":\"curl/7.64.31\"")); + assertThat(result, containsString("\"http.response_content_length\":3234")); + assertThat(result, containsString("\"net.peer.ip\":\"remote-addr\"")); + assertThat(result, containsString("\"net.peer.host\":\"remote-host\"")); + } + + private void thenDoesNotContainHttpFlavor() { + assertThat(result, not(containsString("http.flavor"))); + } + + private void thenNullEmptyAndDashFieldsAreNotIncluded() { + assertThat(result, not(containsString("entity.guid"))); + assertThat(result, not(containsString("entity.type"))); + assertThat(result, not(containsString("entity.name"))); + } + + private void thenPopulatedFieldsAreIncluded() { + assertThat(result, containsString("\"trace.id\":\"some-trace\"")); + } + + private void thenHttpTargetShouldBeSafe(String expected) { + assertThat(result, containsString("\"http.target\":\"" + expected + "\"")); + } + + @Mock + IAccessEvent mockEvent; + + private String result; +} \ No newline at end of file diff --git a/examples/dropwizard-app/build.gradle.kts b/examples/dropwizard-app/build.gradle.kts index 99610cd..d5e58c7 100644 --- a/examples/dropwizard-app/build.gradle.kts +++ b/examples/dropwizard-app/build.gradle.kts @@ -12,10 +12,10 @@ repositories { dependencies { implementation("io.dropwizard:dropwizard-core:1.3.14") implementation(project(":dropwizard")) - implementation("com.newrelic.agent.java:newrelic-api:7.6.0") + implementation("com.newrelic.agent.java:newrelic-api:9.1.0") } -configure { +java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } @@ -29,10 +29,10 @@ val jar by tasks.getting(Jar::class) { val execTask by tasks.register("start", JavaExec::class) { dependsOn("jar") classpath = sourceSets.main.get().runtimeClasspath + mainClass.set("com.newrelic.testapps.dropwizard.Main") jvmArgs = listOf( "-javaagent:${rootProject.projectDir}/lib/newrelic.jar" ) - main = "com.newrelic.testapps.dropwizard.Main" args = listOf( "server", "$projectDir/test.yml" diff --git a/examples/dropwizard3-app/build.gradle.kts b/examples/dropwizard3-app/build.gradle.kts new file mode 100644 index 0000000..daa3833 --- /dev/null +++ b/examples/dropwizard3-app/build.gradle.kts @@ -0,0 +1,41 @@ +plugins { + java +} + +group = "com.newrelic.logging" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + implementation("io.dropwizard:dropwizard-core:3.0.17") + implementation(project(":dropwizard3")) + implementation("com.newrelic.agent.java:newrelic-api:9.1.0") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + +val jar by tasks.getting(Jar::class) { + manifest { + attributes(mapOf("Main-Class" to "com.newrelic.testapps.dropwizard.Main")) + } +} + +val execTask by tasks.register("start", JavaExec::class) { + dependsOn("jar") + classpath = sourceSets.main.get().runtimeClasspath + mainClass.set("com.newrelic.testapps.dropwizard.Main") + jvmArgs = listOf( + "-javaagent:${rootProject.projectDir}/lib/newrelic.jar" + ) + args = listOf( + "server", + "$projectDir/test.yml" + ) +} diff --git a/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/AppConfiguration.java b/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/AppConfiguration.java new file mode 100644 index 0000000..f7bb0d7 --- /dev/null +++ b/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/AppConfiguration.java @@ -0,0 +1,10 @@ +/* + * Copyright 2019 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.newrelic.testapps.dropwizard; + +import io.dropwizard.core.Configuration; + +public class AppConfiguration extends Configuration { +} diff --git a/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Hello.java b/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Hello.java new file mode 100644 index 0000000..1372439 --- /dev/null +++ b/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Hello.java @@ -0,0 +1,23 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.newrelic.testapps.dropwizard; + +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +@Path("/hello/{name}") +public class Hello { + @GET + public String sayHello(@PathParam("name") String name) { + return "Hello, " + name + "!"; + } + + @POST + public String includePostdata(String body) { + return "{\"request_length\":" + body.length() + "}"; + } +} diff --git a/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Main.java b/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Main.java new file mode 100644 index 0000000..f6c5381 --- /dev/null +++ b/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Main.java @@ -0,0 +1,115 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.newrelic.testapps.dropwizard; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; +import com.newrelic.api.agent.Trace; +import com.newrelic.logging.dropwizard3.access.LinkingMetadataAsRequestAttributesFilter; +import io.dropwizard.core.Application; +import io.dropwizard.core.setup.Environment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import java.util.Arrays; +import java.util.List; + +public class Main extends Application { + public static void main(String[] args) throws Exception { + new Main().run(args); + } + + private static final Logger logger = LoggerFactory.getLogger(Main.class.getName()); + + @Override + public void run(AppConfiguration configuration, Environment environment) { + // Enable MDC collection + // Alternatively, this could be set using the environment variable NEW_RELIC_LOG_EXTENSION_ADD_MDC + System.setProperty("newrelic.log_extension.add_mdc", "true"); + + logger.info("\uD83C\uDF89 Starting the program."); + + // Add MDC data + MDC.put("contextKey1", "contextData1"); + MDC.put("contextKey2", "contextData2"); + MDC.put("contextKey3", "contextData3"); + + // Set up our resource so Jersey can find it. + environment.jersey().register(new Hello()); + + // This is step 2 of 2 for using New Relic linking metadata with Dropwizard request logging. + // This filter will add most linking metadata as request attributes. From there, it can be + // included in the newrelic-access-json format, or in a customized PatternLayout under %requestAttribute. + environment.servlets().addFilter("trace-into-request", new LinkingMetadataAsRequestAttributesFilter()) + .addMappingForUrlPatterns(null, true, "/*"); + + List threads = Arrays.asList( + new Thread(() -> transactionWithError("Here is an error")), + new Thread(() -> transactionWithWarn("Here is a warning")), + new Thread(() -> transactionWithInfo("Here is an informational message")), + new Thread(Main::transactionWithAsyncComponent) + ); + + threads.forEach(Thread::start); + threads.forEach(t -> { + try { + t.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + + logger.info("Program complete."); + + // Clear MDC data + MDC.clear(); + } + + @Trace(dispatcher = true) + private static void transactionWithInfo(String message) { + logger.info(message); + } + + @Trace(dispatcher = true) + private static void transactionWithWarn(String message) { + logger.warn(message); + callSecondaryTrace(); + } + + @Trace + private static void callSecondaryTrace() { + logger.error("This is a secondary error called by the warning transaction."); + } + + @Trace(dispatcher = true) + private static void transactionWithError(String message) { + logger.error(message); + Throwable t = new RuntimeException("Whoops!"); + NewRelic.noticeError(t); + logger.error("this contains a throwable", t); + logger.error("This is a secondary error in the same span as the error message"); + } + + @Trace(dispatcher = true) + private static void transactionWithAsyncComponent() { + logger.error("This is the first message from an async transaction using {} passing", "token"); + final Token token = NewRelic.getAgent().getTransaction().getToken(); + Thread t = new Thread(() -> theAsyncMethod(token)); + t.start(); + try { + t.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + logger.warn("Resuming the sync method after the async method"); + } + + @Trace(async = true) + private static void theAsyncMethod(Token token) { + logger.error("The actual async method: token linkAndExpire success? " + token.linkAndExpire()); + } + +} diff --git a/examples/dropwizard3-app/test.yml b/examples/dropwizard3-app/test.yml new file mode 100644 index 0000000..5f9a730 --- /dev/null +++ b/examples/dropwizard3-app/test.yml @@ -0,0 +1,28 @@ +# This is half the work for using New Relic linking metadata with Dropwizard request logging. +# The `newrelic-access-json` layout type is a JSON format stylized after +# OpenTelemetry attributes. The other half is adding the servlet filter (see Main.java in this project). +server: + requestLog: + appenders: + - type: console # Any standard appender should work here. Don't use a newrelic appender as it doesn't include the appropriate field names. + layout: + type: newrelic-access-json # This layout is what you want. + +logging: + level: INFO + appenders: + # This stanza uses the newrelic-file appender with the newrelic-json layout designed + # for use with Logs-in-Context. This should include all appropriate linking metadata. + - type: newrelic-file + layout: + type: newrelic-json + archive: false + currentLogFilename: ./logs/my-app.log + # This stanza uses the newrelic-console appender with a standard format. Because + # linking metadata is included in the MDC, the attributes can be extracted in almost any layout. + # The `replace` parameter in the PatternLayout retrieves all MDC values and strips off the + # `NewRelic:` prefix from all linking metadata keys. + - type: newrelic-console + logFormat: "%date{ISO8601} %c %-5p: %m %replace(%mdc{}){'NewRelic:', ''}%n" + layout: + type: log-format \ No newline at end of file diff --git a/examples/jul-app/build.gradle.kts b/examples/jul-app/build.gradle.kts index 1005c25..f6ec5a0 100644 --- a/examples/jul-app/build.gradle.kts +++ b/examples/jul-app/build.gradle.kts @@ -16,13 +16,13 @@ dependencies { } -configure { +java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } application { - mainClassName = "com.newrelic.testapps.jul.Main" + mainClass.set("com.newrelic.testapps.jul.Main") applicationDefaultJvmArgs += listOf( "-javaagent:${rootProject.projectDir}/lib/newrelic.jar", "-Djava.util.logging.config.file=src/main/resources/logging.properties" diff --git a/examples/log4j1-app/build.gradle.kts b/examples/log4j1-app/build.gradle.kts index 1ea0f96..28d7a9e 100644 --- a/examples/log4j1-app/build.gradle.kts +++ b/examples/log4j1-app/build.gradle.kts @@ -19,13 +19,13 @@ dependencies { } -configure { +java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } application { - mainClassName = "com.newrelic.testapps.log4j1.Main" + mainClass.set("com.newrelic.testapps.log4j1.Main") applicationDefaultJvmArgs += listOf( "-javaagent:${rootProject.projectDir}/lib/newrelic.jar" ) diff --git a/examples/log4j2-app/build.gradle.kts b/examples/log4j2-app/build.gradle.kts index 292bb0e..d4ae135 100644 --- a/examples/log4j2-app/build.gradle.kts +++ b/examples/log4j2-app/build.gradle.kts @@ -21,13 +21,13 @@ dependencies { } -configure { +java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } application { - mainClassName = "com.newrelic.testapps.log4j2.Main" + mainClass.set("com.newrelic.testapps.log4j2.Main") applicationDefaultJvmArgs += listOf( "-javaagent:${rootProject.projectDir}/lib/newrelic.jar", "-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector" diff --git a/examples/logback-app/build.gradle.kts b/examples/logback-app/build.gradle.kts index 4af39dd..705e9a6 100644 --- a/examples/logback-app/build.gradle.kts +++ b/examples/logback-app/build.gradle.kts @@ -20,12 +20,12 @@ dependencies { } -configure { +java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } application { - mainClassName = "com.newrelic.testapps.logback.Main" + mainClass.set("com.newrelic.testapps.logback.Main") applicationDefaultJvmArgs += listOf("-javaagent:${rootProject.projectDir}/lib/newrelic.jar") } diff --git a/examples/logback11-app/build.gradle.kts b/examples/logback11-app/build.gradle.kts index ea4917c..04b3d69 100644 --- a/examples/logback11-app/build.gradle.kts +++ b/examples/logback11-app/build.gradle.kts @@ -20,12 +20,12 @@ dependencies { } -configure { +java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } application { - mainClassName = "com.newrelic.testapps.logback11.Main" + mainClass.set("com.newrelic.testapps.logback11.Main") applicationDefaultJvmArgs += listOf("-javaagent:${rootProject.projectDir}/lib/newrelic.jar") } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661..260504c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/performance/log4j2-perf/build.gradle.kts b/performance/log4j2-perf/build.gradle.kts index 311181e..1919bf4 100644 --- a/performance/log4j2-perf/build.gradle.kts +++ b/performance/log4j2-perf/build.gradle.kts @@ -26,7 +26,7 @@ configure { } application { - mainClassName = "PerformanceMain" + mainClass.set("PerformanceMain") applicationDefaultJvmArgs += listOf( "-javaagent:${rootProject.projectDir}/lib/newrelic.jar", "-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector" @@ -34,21 +34,21 @@ application { } task("executeNoAgent", JavaExec::class) { - main = "PerformanceMain" + mainClass.set("PerformanceMain") classpath = sourceSets["main"].runtimeClasspath args = listOf("INFO", "No_Agent") jvmArgs("-Xmx1024m") } task("executeWithAgent", JavaExec::class) { - main = "PerformanceMain" + mainClass.set("PerformanceMain") classpath = sourceSets["main"].runtimeClasspath args = listOf("INFO", "With_Agent") jvmArgs("-Xmx1024m", "-javaagent:${rootProject.projectDir}/lib/newrelic.jar") } task("executeNoAgentAsync", JavaExec::class) { - main = "PerformanceMain" + mainClass.set("PerformanceMain") classpath = sourceSets["main"].runtimeClasspath args = listOf("INFO", "No_Agent_Async") jvmArgs("-Xmx1024m") @@ -56,7 +56,7 @@ task("executeNoAgentAsync", JavaExec::class) { } task("executeWithAgentAsync", JavaExec::class) { - main = "PerformanceMain" + mainClass.set("PerformanceMain") classpath = sourceSets["main"].runtimeClasspath args = listOf("INFO", "With_Agent_Async") jvmArgs("-Xmx1024m", "-javaagent:${rootProject.projectDir}/lib/newrelic.jar") diff --git a/settings.gradle b/settings.gradle index e7fc11d..5ecb5a5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,6 +6,7 @@ include 'logback11' include 'logback13' include 'jul' include 'dropwizard' +include 'dropwizard3' include 'core' include 'core-test' @@ -15,6 +16,7 @@ include 'examples:logback-app' include 'examples:logback11-app' include 'examples:jul-app' include 'examples:dropwizard-app' +include 'examples:dropwizard3-app' include 'performance:log4j2-perf' From aeab6412e91124b7d8c8f3047c42c5c37b983a18 Mon Sep 17 00:00:00 2001 From: Kate Anderson Date: Mon, 13 Apr 2026 15:42:34 -0700 Subject: [PATCH 2/5] Update README and copyright headers --- dropwizard3/README.md | 6 +++--- .../logging/dropwizard3/LogFormatLayoutFactory.java | 2 +- .../logging/dropwizard3/NewRelicAsyncAppenderFactory.java | 2 +- .../logging/dropwizard3/NewRelicConsoleAppenderFactory.java | 2 +- .../logging/dropwizard3/NewRelicFileAppenderFactory.java | 2 +- .../logging/dropwizard3/NewRelicJsonLayoutFactory.java | 2 +- .../com/newrelic/logging/dropwizard3/access/AccessLog.java | 2 +- .../access/LinkingMetadataAsRequestAttributesFilter.java | 2 +- .../dropwizard3/access/NewRelicAccessJsonLayout.java | 2 +- .../dropwizard3/access/NewRelicAccessJsonLayoutFactory.java | 2 +- .../logging/dropwizard3/NewRelicAppenderFactoryTest.java | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/dropwizard3/README.md b/dropwizard3/README.md index bcc1fee..da222e6 100644 --- a/dropwizard3/README.md +++ b/dropwizard3/README.md @@ -1,13 +1,13 @@ # The New Relic Dropwizard Logging Extension -This is a lift of the existing Dropwizard Logging Extension module. It adds support for Dropwizard logging >= v3.x. +This is a lift of the existing Dropwizard Logging Extension module. It adds support for Dropwizard logging v3.x. The primary difference in this module is that it requires Java 11+ (a transitive requirement of Dropwizard v3.x). ## Preconditions -1. Dropwizard v3.x or higher must be configured and working in the application with the Dropwizard appenders and logging factory. -2. You must be using Java 11 or higher (required to run Dropwizard 3+). +1. Dropwizard v3.x must be configured and working in the application with the Dropwizard appenders and logging factory. +2. You must be using Java 11 or higher (required to run Dropwizard 3). 3. The New Relic Java agent must be enabled using the `-javaagent` command-line parameter. 4. You must be using at least version 5.6.0 of the Java Agent. diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/LogFormatLayoutFactory.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/LogFormatLayoutFactory.java index ac39f16..81255b2 100644 --- a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/LogFormatLayoutFactory.java +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/LogFormatLayoutFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2019. New Relic Corporation. All rights reserved. + * Copyright 2026. New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicAsyncAppenderFactory.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicAsyncAppenderFactory.java index 319e7bf..a9a2a98 100644 --- a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicAsyncAppenderFactory.java +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicAsyncAppenderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Relic Corporation. All rights reserved. + * Copyright 2026 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ package com.newrelic.logging.dropwizard3; diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicConsoleAppenderFactory.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicConsoleAppenderFactory.java index 4f8d6e0..d4274b1 100644 --- a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicConsoleAppenderFactory.java +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicConsoleAppenderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Relic Corporation. All rights reserved. + * Copyright 2026 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ package com.newrelic.logging.dropwizard3; diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicFileAppenderFactory.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicFileAppenderFactory.java index 33dad89..2b7c5cc 100644 --- a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicFileAppenderFactory.java +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicFileAppenderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Relic Corporation. All rights reserved. + * Copyright 2026 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ package com.newrelic.logging.dropwizard3; diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicJsonLayoutFactory.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicJsonLayoutFactory.java index 7269123..6ce1f7b 100644 --- a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicJsonLayoutFactory.java +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/NewRelicJsonLayoutFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Relic Corporation. All rights reserved. + * Copyright 2026 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ package com.newrelic.logging.dropwizard3; diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/AccessLog.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/AccessLog.java index e8cea2b..10d9ed8 100644 --- a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/AccessLog.java +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/AccessLog.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 New Relic Corporation. All rights reserved. + * Copyright 2026 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ package com.newrelic.logging.dropwizard3.access; diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilter.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilter.java index e7a6fa6..f9de88c 100644 --- a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilter.java +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 New Relic Corporation. All rights reserved. + * Copyright 2026 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ package com.newrelic.logging.dropwizard3.access; diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayout.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayout.java index 9734f9e..0701ae4 100644 --- a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayout.java +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayout.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 New Relic Corporation. All rights reserved. + * Copyright 2026 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ package com.newrelic.logging.dropwizard3.access; diff --git a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutFactory.java b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutFactory.java index fa984bc..50be881 100644 --- a/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutFactory.java +++ b/dropwizard3/src/main/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 New Relic Corporation. All rights reserved. + * Copyright 2026 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ package com.newrelic.logging.dropwizard3.access; diff --git a/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/NewRelicAppenderFactoryTest.java b/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/NewRelicAppenderFactoryTest.java index e7803d7..bf3401e 100644 --- a/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/NewRelicAppenderFactoryTest.java +++ b/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/NewRelicAppenderFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019. New Relic Corporation. All rights reserved. + * Copyright 2026. New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ From 600ceac589bb7d7c68d6d4c3d25c14d37329de07 Mon Sep 17 00:00:00 2001 From: Kate Anderson Date: Mon, 13 Apr 2026 17:01:16 -0700 Subject: [PATCH 3/5] Change servlet dependency to jakarta 4, update example README --- dropwizard3/build.gradle.kts | 3 +-- examples/README.md | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dropwizard3/build.gradle.kts b/dropwizard3/build.gradle.kts index c149033..3333ae2 100644 --- a/dropwizard3/build.gradle.kts +++ b/dropwizard3/build.gradle.kts @@ -22,9 +22,8 @@ configurations["compileOnly"].extendsFrom(includeInJar) dependencies { implementation("io.dropwizard:dropwizard-logging:3.0.0") - implementation("com.github.ben-manes.caffeine:caffeine:3.1.5") implementation("io.dropwizard:dropwizard-request-logging:3.0.0") - implementation("javax.servlet:javax.servlet-api:3.1.0") + implementation("jakarta.servlet:jakarta.servlet-api:4.0.4") implementation("com.newrelic.agent.java:newrelic-api:9.1.0") includeInJar(project(":logback")) { diff --git a/examples/README.md b/examples/README.md index 53a8946..c94d430 100644 --- a/examples/README.md +++ b/examples/README.md @@ -19,6 +19,11 @@ You must follow some initial steps: Run the application with `./gradlew :examples:dropwizard-app:start`. Some early log messages include transactions. The decorated log messages will be written to `examples/dropwizard-app/logs/my-app.log`. See [test.yml](dropwizard-app/test.yml) for the configuration. +### Dropwizard 3 + +Run the application with `./gradlew :examples:dropwizard3-app:start`. Some early log messages include transactions. +The decorated log messages will be written to `examples/dropwizard-app/logs/my-app.log`. See [test.yml](dropwizard-app/test.yml) for the configuration. + ### `java.util.logging` Run the application with `./gradlew :examples:jul-app:run`. When the application completes, From 3341717642212611527d74ee42c4fbe786519640 Mon Sep 17 00:00:00 2001 From: Kate Anderson Date: Mon, 13 Apr 2026 17:18:01 -0700 Subject: [PATCH 4/5] Update missed copyright headers --- .../access/LinkingMetadataAsRequestAttributesFilterTest.java | 2 +- .../dropwizard3/access/NewRelicAccessJsonLayoutTest.java | 2 +- .../java/com/newrelic/testapps/dropwizard/AppConfiguration.java | 2 +- .../src/main/java/com/newrelic/testapps/dropwizard/Hello.java | 2 +- .../src/main/java/com/newrelic/testapps/dropwizard/Main.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilterTest.java b/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilterTest.java index 6df08da..48154a8 100644 --- a/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilterTest.java +++ b/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/LinkingMetadataAsRequestAttributesFilterTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019. New Relic Corporation. All rights reserved. + * Copyright 2026. New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutTest.java b/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutTest.java index efd3a2e..df7c099 100644 --- a/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutTest.java +++ b/dropwizard3/src/test/java/com/newrelic/logging/dropwizard3/access/NewRelicAccessJsonLayoutTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019. New Relic Corporation. All rights reserved. + * Copyright 2026. New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/AppConfiguration.java b/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/AppConfiguration.java index f7bb0d7..2e45104 100644 --- a/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/AppConfiguration.java +++ b/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/AppConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Relic Corporation. All rights reserved. + * Copyright 2026 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ package com.newrelic.testapps.dropwizard; diff --git a/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Hello.java b/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Hello.java index 1372439..ef2333d 100644 --- a/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Hello.java +++ b/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Hello.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 New Relic Corporation. All rights reserved. + * Copyright 2026 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ package com.newrelic.testapps.dropwizard; diff --git a/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Main.java b/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Main.java index f6c5381..e3d5c55 100644 --- a/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Main.java +++ b/examples/dropwizard3-app/src/main/java/com/newrelic/testapps/dropwizard/Main.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 New Relic Corporation. All rights reserved. + * Copyright 2026 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ package com.newrelic.testapps.dropwizard; From 648f5555a1286abcd0da3eb475cf74cc97fd5e3a Mon Sep 17 00:00:00 2001 From: Kate Anderson Date: Tue, 14 Apr 2026 14:31:41 -0700 Subject: [PATCH 5/5] Upgrade spotbugs version for dropwizard3 --- dropwizard3/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dropwizard3/build.gradle.kts b/dropwizard3/build.gradle.kts index 3333ae2..f981743 100644 --- a/dropwizard3/build.gradle.kts +++ b/dropwizard3/build.gradle.kts @@ -1,6 +1,6 @@ plugins { java - id("com.github.spotbugs").version("4.4.4") + id("com.github.spotbugs").version("4.8.0") } group = "com.newrelic.logging"