Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repositories {
subprojects {
tasks.withType<Test>().all {
useJUnitPlatform()
reports.junitXml.isEnabled = true
reports.junitXml.required = true
}

tasks.withType<Javadoc>().all {
Expand Down
2 changes: 1 addition & 1 deletion dropwizard/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ tasks.withType<Javadoc> {
enabled = true
}

configure<JavaPluginConvention> {
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
Expand Down
100 changes: 100 additions & 0 deletions dropwizard3/README.md
Original file line number Diff line number Diff line change
@@ -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 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.
83 changes: 83 additions & 0 deletions dropwizard3/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
plugins {
java
id("com.github.spotbugs").version("4.8.0")
}

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("io.dropwizard:dropwizard-request-logging:3.0.0")
implementation("jakarta.servlet:jakarta.servlet-api:4.0.4")

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<Javadoc> {
enabled = true
}

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
}
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

tasks.withType<JavaCompile>().configureEach {
options.release.set(11)
}


tasks.register<Jar>("sourcesJar") {
from(sourceSets.main.get().allJava)
archiveClassifier.set("sources")
}

tasks.register<Jar>("javadocJar") {
from(tasks.javadoc)
archiveClassifier.set("javadoc")
}

apply(from = "$rootDir/gradle/publish.gradle.kts")

tasks.withType<com.github.spotbugs.snom.SpotBugsTask> {
reports.create("html") {
isEnabled = true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2026. 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.
* <p>
* The other option is to fully remove {@literal layout} from the YAML.
*/
@JsonTypeName("log-format")
public class LogFormatLayoutFactory implements DiscoverableLayoutFactory<ILoggingEvent> {
private String logFormat;

void setLogFormat(String logFormat) {
this.logFormat = logFormat;
}

@Override
public LayoutBase<ILoggingEvent> build(LoggerContext context, TimeZone timeZone) {
DropwizardLayout layout = new DropwizardLayout(context, timeZone);
if (logFormat != null && !logFormat.isEmpty()) {
layout.setPattern(logFormat);
}
return layout;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2026 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<ILoggingEvent> build() {
return new NewRelicAsyncAppender();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2026 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<ILoggingEvent> {
@Override
protected Appender<ILoggingEvent> wrapAsync(Appender<ILoggingEvent> appender, AsyncAppenderFactory<ILoggingEvent> asyncAppenderFactory) {
return super.wrapAsync(appender, new NewRelicAsyncAppenderFactory());
}

@Override
protected Appender<ILoggingEvent> wrapAsync(Appender<ILoggingEvent> appender, AsyncAppenderFactory<ILoggingEvent> asyncAppenderFactory, Context context) {
return super.wrapAsync(appender, new NewRelicAsyncAppenderFactory(), context);
}

@Override
protected LayoutBase<ILoggingEvent> buildLayout(LoggerContext context, LayoutFactory<ILoggingEvent> defaultLayoutFactory) {
if (layout instanceof LogFormatLayoutFactory) {
((LogFormatLayoutFactory)layout).setLogFormat(logFormat);
}

return super.buildLayout(context, defaultLayoutFactory);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2026 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<ILoggingEvent> {
@Override
protected Appender<ILoggingEvent> wrapAsync(Appender<ILoggingEvent> appender, AsyncAppenderFactory<ILoggingEvent> asyncAppenderFactory) {
return super.wrapAsync(appender, new NewRelicAsyncAppenderFactory());
}

@Override
protected Appender<ILoggingEvent> wrapAsync(Appender<ILoggingEvent> appender, AsyncAppenderFactory<ILoggingEvent> asyncAppenderFactory, Context context) {
return super.wrapAsync(appender, new NewRelicAsyncAppenderFactory(), context);
}

@Override
protected LayoutBase<ILoggingEvent> buildLayout(LoggerContext context, LayoutFactory<ILoggingEvent> defaultLayoutFactory) {
if (layout instanceof LogFormatLayoutFactory) {
((LogFormatLayoutFactory)layout).setLogFormat(logFormat);
}

return super.buildLayout(context, defaultLayoutFactory);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2026 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<ILoggingEvent> {
@Override
public LayoutBase<ILoggingEvent> build(LoggerContext context, TimeZone timeZone) {
return new NewRelicJsonLayout();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright 2026 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() {
}
}
Loading
Loading