Skip to content

Commit 02fa4b3

Browse files
authored
Merge pull request #2452 from newrelic/otel-logs-api
Otel logs api
2 parents 01ccb3e + 8f4c713 commit 02fa4b3

25 files changed

+1433
-51
lines changed
Lines changed: 152 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,86 @@
11
# OpenTelemetry Instrumentation
22

3-
This instrumentation module weaves parts of the OpenTelemetry SDK to incorporate bits of OpenTelemetry functionality into the New Relic Java agent.
3+
This instrumentation module weaves parts of the OpenTelemetry SDK to incorporate bits of OpenTelemetry functionality into the New Relic Java agent.
44

5-
Specifically it can:
6-
* Detect OpenTelemetry Spans and include them in New Relic Java agent traces.
5+
Specifically, it can:
6+
7+
* Detect OpenTelemetry Spans and add them to New Relic Java agent traces as New Relic Spans.
8+
* Detect OpenTelemetry LogRecords and report them as New Relic LogEvents.
79
* Detect OpenTelemetry dimensional metrics and report them to the APM entity being monitored by the Java agent.
810
* Autoconfigure the OpenTelemetry SDK so that OpenTelemetry data is sent to New Relic and properly associated with an APM entity guid.
911

12+
## OpenTelemetry Configuration
13+
14+
To use the OpenTelemetry functionality incorporated into the New Relic Java agent you must enable the following config options:
15+
16+
```commandline
17+
-Dotel.java.global-autoconfigure.enabled=true
18+
```
19+
1020
## New Relic Java Agent Configuration
1121

1222
To use the OpenTelemetry Span and dimensional metric functionality incorporated into the New Relic Java agent you must enable the following config options:
1323

1424
Configuration via yaml:
15-
```
16-
opentelemetry:
17-
sdk:
18-
autoconfigure:
19-
enabled: true
20-
spans:
21-
enabled: true
25+
26+
```yaml
27+
opentelemetry:
28+
sdk:
29+
autoconfigure:
30+
enabled: true
31+
spans:
32+
enabled: true
33+
logs:
34+
enabled: true
35+
instrumentation:
36+
specific-instrumentation-scope-name-1:
37+
enabled: true
38+
specific-instrumentation-scope-name-2:
39+
enabled: true
2240
```
2341
2442
Configuration via system property:
43+
2544
```
26-
-Dopentelemetry.sdk.autoconfigure.enabled=true
27-
-Dopentelemetry.sdk.spans.enabled=true
45+
-Dnewrelic.config.opentelemetry.sdk.autoconfigure.enabled=true
46+
-Dnewrelic.config.opentelemetry.sdk.spans.enabled=true
47+
-Dnewrelic.config.opentelemetry.sdk.logs.enabled=true
48+
49+
# To disable specific OpenTelemetry instrumentation scopes, use:
50+
-Dnewrelic.config.opentelemetry.instrumentation.[SPECIFIC_INSTRUMENTATION_SCOPE_NAME].enabled=true
2851
```
2952

3053
Configuration via environment variable:
54+
3155
```
3256
NEW_RELIC_OPENTELEMETRY_SDK_AUTOCONFIGURE_ENABLED=true
3357
NEW_RELIC_OPENTELEMETRY_SDK_SPANS_ENABLED=true
58+
NEW_RELIC_OPENTELEMETRY_SDK_LOGS_ENABLED=true
59+
60+
# To disable specific OpenTelemetry instrumentation scopes, use:
61+
NEW_RELIC_OPENTELEMETRY_INSTRUMENTATION_[SPECIFIC_INSTRUMENTATION_SCOPE_NAME]_ENABLED=true
3462
```
3563

3664
## OpenTelemetry Dimensional Metrics
3765

38-
OpenTelemetry APIs can be used to create dimensional metrics which will be detected by the New Relic Java agent and reported to the APM entity being monitored by the New Relic Java agent.
66+
OpenTelemetry APIs can be used to create dimensional metrics which will be detected by the New Relic Java agent and reported to the APM entity being monitored
67+
by the New Relic Java agent.
3968

4069
To use this functionality, enable the feature as documented above, add the required `opentelemetry` dependencies to your application:
70+
4171
```groovy
4272
implementation(platform("io.opentelemetry:opentelemetry-bom:1.44.1"))
43-
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
44-
implementation("io.opentelemetry:opentelemetry-exporter-otlp")
73+
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
74+
implementation("io.opentelemetry:opentelemetry-exporter-otlp")
4575
```
4676

4777
Then utilize the OpenTelemetry APIs to record dimensional metrics:
78+
4879
```java
4980
LongCounter longCounter = GlobalOpenTelemetry.get().getMeterProvider().get("my-application").counterBuilder("my.application.counter").build();
50-
longCounter.add(1, Attributes.of(AttributeKey.stringKey("foo"), "bar"));
81+
longCounter.
82+
83+
add(1,Attributes.of(AttributeKey.stringKey("foo"), "bar"));
5184
```
5285

5386
Any recorded dimensional metrics can be found in the Metrics Explorer for the associated APM entity and can be used to build custom dashboards.
@@ -58,36 +91,125 @@ Documented below are several approaches for incorporating OpenTelemetry Spans in
5891

5992
### `@WithSpan` Annotation
6093

61-
The New Relic Java agent will detect usage of the OpenTelemetry [@WithSpan](https://opentelemetry.io/docs/zero-code/java/agent/annotations/) annotation. The `@WithSpan` annotation can be used as an alternative to the `@Trace` annotation.
94+
The New Relic Java agent will detect usage of the OpenTelemetry [@WithSpan](https://opentelemetry.io/docs/zero-code/java/agent/annotations/) annotation. The
95+
`@WithSpan` annotation can be used as an alternative to the `@Trace` annotation.
6296

6397
This does not currently support the following config options:
98+
6499
* [Suppressing @WithSpan instrumentation](https://opentelemetry.io/docs/zero-code/java/agent/annotations/#suppressing-withspan-instrumentation)
65100
* [Creating spans around methods with otel.instrumentation.methods.include](https://opentelemetry.io/docs/zero-code/java/agent/annotations/#creating-spans-around-methods-with-otelinstrumentationmethodsinclude)
66101

67-
Note that OpenTelemetry config properties can be set through environment or system properties, like our agent, and eventually through a config file. We can use our existing OpenTelemetry instrumentation model to get access to the normalized version of the instrumentation settings to include and exclude methods and pass those to the core agent through the bridge.
102+
Note that OpenTelemetry config properties can be set through environment or system properties, like our agent, and eventually through a config file. We can use
103+
our existing OpenTelemetry instrumentation model to get access to the normalized version of the instrumentation settings to include and exclude methods and pass
104+
those to the core agent through the bridge.
68105

69106
See `ClassTransformerConfigImpl.java` for implementation details of the `@WithSpan` annotation.
70107

71108
### Spans Emitted From OpenTelemetry Instrumentation
72109

73-
The New Relic Java agent will detect Spans emitted by [OpenTelemetry instrumentation](https://opentelemetry.io/docs/languages/java/instrumentation/). It does this by weaving the `io.opentelemetry.sdk.trace.SdkTracerProvider` so that it will create a New Relic Tracer each time an OpenTelemetry Span is started and weaving the `io.opentelemetry.context.Context` to propagate context between New Relic and OpenTelemetry Spans.
110+
The New Relic Java agent will detect Spans emitted by [OpenTelemetry instrumentation](https://opentelemetry.io/docs/languages/java/instrumentation/). It does
111+
this by weaving the `io.opentelemetry.sdk.trace.SdkTracerProvider` so that it will create a New Relic Tracer each time an OpenTelemetry Span is started and
112+
weaving the `io.opentelemetry.context.Context` to propagate context between New Relic and OpenTelemetry Spans.
74113

75-
Currently, the New Relic Java agent does not load any OpenTelemetry instrumentation it simply detects Spans emitted by OpenTelemetry manual instrumentation, native instrumentation, library instrumentation, or zero code instrumentation (i.e. bytecode instrumentation that would also require running the OpenTelemetry Java agent).
114+
Currently, the New Relic Java agent does not load any OpenTelemetry instrumentation it simply detects Spans emitted by OpenTelemetry manual instrumentation,
115+
native instrumentation, library instrumentation, or zero code instrumentation (i.e. bytecode instrumentation that would also require running the OpenTelemetry
116+
Java agent).
76117

77118
Depending on the OpenTelemetry Span `SpanKind`, it may result in the New Relic Java agent starting a transaction (when one doesn't already exist).
78119

79120
* `SpanKind.INTERNAL`
80-
* Creating a span with no `SpanKind`, which defaults to `SpanKind.INTERNAL`, will not start a transaction
81-
* If `SpanKind.INTERNAL` spans occur within an already existing New Relic transaction they will be included in the trace
121+
* Creating a span with no `SpanKind`, which defaults to `SpanKind.INTERNAL`, will not start a transaction
122+
* If `SpanKind.INTERNAL` spans occur within an already existing New Relic transaction they will be included in the trace
82123
* `SpanKind.CLIENT`
83-
* Creating a span with `SpanKind.CLIENT` will not start a transaction. If a `CLIENT` span has certain db attributes it will be treated as a DB span, and other specific attributes will cause it to be treated as an external span
84-
* If `SpanKind.CLIENT` spans occur within an already existing New Relic transaction they will be included in the trace
124+
* Creating a span with `SpanKind.CLIENT` will not start a transaction. If a `CLIENT` span has certain db attributes it will be treated as a DB span, and
125+
other specific attributes will cause it to be treated as an external span
126+
* If `SpanKind.CLIENT` spans occur within an already existing New Relic transaction they will be included in the trace
85127
* `SpanKind.SERVER`
86-
* Creating a span with `SpanKind.SERVER` will start a `WebTransaction/Uri/*` transaction.
87-
* If `SpanKind.SERVER` spans occur within an already existing New Relic transaction they will be included in the trace
128+
* Creating a span with `SpanKind.SERVER` will start a `WebTransaction/Uri/*` transaction.
129+
* If `SpanKind.SERVER` spans occur within an already existing New Relic transaction they will be included in the trace
88130
* `SpanKind.CONSUMER`
89-
* Creating a span with `SpanKind.CONSUMER` will start a `OtherTransaction/*` transaction.
90-
* If `SpanKind.CONSUMER` spans occur within an already existing New Relic transaction they will be included in the trace
131+
* Creating a span with `SpanKind.CONSUMER` will start a `OtherTransaction/*` transaction.
132+
* If `SpanKind.CONSUMER` spans occur within an already existing New Relic transaction they will be included in the trace
91133
* `SpanKind.PRODUCER`
92-
* Creating a span with `SpanKind.PRODUCER` will not start a transaction. There is no explicit processing for `PRODUCER` spans currently.
93-
* If `SpanKind.PRODUCER` spans occur within an already existing New Relic transaction they will be included in the trace (though it's effectively no different from a `SpanKind.INTERNAL` span)
134+
* Creating a span with `SpanKind.PRODUCER` will not start a transaction. There is no explicit processing for `PRODUCER` spans currently.
135+
* If `SpanKind.PRODUCER` spans occur within an already existing New Relic transaction they will be included in the trace (though it's effectively no
136+
different from a `SpanKind.INTERNAL` span)
137+
138+
## OpenTelemetry Logs
139+
140+
OpenTelemetry APIs can be used to create log records which will be detected by the New Relic Java agent and reported to the APM entity being monitored by the
141+
New Relic Java agent. The log records will be reported as log events in New Relic, which will be associated with a transaction if the logging occurred within
142+
one.
143+
144+
To use this functionality, enable the feature as documented above, add the required `opentelemetry` dependencies to your application:
145+
146+
```groovy
147+
implementation(platform("io.opentelemetry:opentelemetry-bom:1.28.0"))
148+
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
149+
implementation("io.opentelemetry:opentelemetry-exporter-logging")
150+
```
151+
152+
Example usage of OpenTelemetry Logs APIs:
153+
154+
```java
155+
// create LogRecordExporter
156+
private static final SystemOutLogRecordExporter systemOutLogRecordExporter = SystemOutLogRecordExporter.create();
157+
158+
// create LogRecordProcessor
159+
private static final LogRecordProcessor logRecordProcessor = SimpleLogRecordProcessor.create(systemOutLogRecordExporter);
160+
161+
// create Attributes
162+
private static final Attributes attributes = Attributes.builder()
163+
.put("service.name", NewRelic.getAgent().getConfig().getValue("app_name", "unknown"))
164+
.put("service.version", "4.5.1")
165+
.put("environment", "production")
166+
.build();
167+
168+
// create Resource
169+
private static final Resource customResource = Resource.create(attributes);
170+
171+
// create SdkLoggerProvider
172+
private static final SdkLoggerProvider sdkLoggerProvider = SdkLoggerProvider.builder()
173+
.addLogRecordProcessor(logRecordProcessor)
174+
.setResource(customResource)
175+
.build();
176+
177+
// create LoggerBuilder
178+
private static final LoggerBuilder loggerBuilder = sdkLoggerProvider
179+
.loggerBuilder("demo-otel-logger")
180+
.setInstrumentationVersion("1.0.0")
181+
.setSchemaUrl("https://opentelemetry.io/schemas/1.0.0");
182+
183+
// create Logger
184+
private static final Logger logger = loggerBuilder.build();
185+
186+
// utility method to build and emit OpenTelemetry log records
187+
public static void emitOTelLogs(Severity severity) {
188+
// create LogRecordBuilder
189+
LogRecordBuilder logRecordBuilder = logger.logRecordBuilder();
190+
191+
Instant now = Instant.now();
192+
logRecordBuilder
193+
// .setContext()
194+
.setBody("Generating OpenTelemetry LogRecord")
195+
.setSeverity(severity)
196+
.setSeverityText("This is the severity text")
197+
.setAttribute(AttributeKey.stringKey("foo"), "bar")
198+
.setObservedTimestamp(now)
199+
.setObservedTimestamp(now.toEpochMilli(), java.util.concurrent.TimeUnit.MILLISECONDS)
200+
.setTimestamp(now)
201+
.setTimestamp(now.toEpochMilli(), java.util.concurrent.TimeUnit.MILLISECONDS);
202+
203+
if (severity == Severity.ERROR) {
204+
try {
205+
throw new RuntimeException("This is a test exception for severity ERROR");
206+
} catch (RuntimeException e) {
207+
logRecordBuilder.setAttribute(AttributeKey.stringKey("exception.message"), e.getMessage());
208+
logRecordBuilder.setAttribute(AttributeKey.stringKey("exception.type"), e.getClass().getName());
209+
logRecordBuilder.setAttribute(AttributeKey.stringKey("exception.stacktrace"), Arrays.toString(e.getStackTrace()));
210+
}
211+
}
212+
213+
logRecordBuilder.emit();
214+
}
215+
```
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
*
66
*/
77

8-
package io.opentelemetry.sdk.trace;
8+
package com.nr.agent.instrumentation.utils;
99

1010
import io.opentelemetry.api.common.Attributes;
1111
import io.opentelemetry.api.common.AttributesBuilder;
1212

1313
import java.util.Map;
1414

1515
/**
16-
* Helper class for adding attributes to Spans
16+
* Helper class for turning a map of attributes into an OTel Attributes object.
1717
*/
1818
public class AttributesHelper {
1919
private AttributesHelper() {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*
66
*/
77

8-
package com.nr.agent.instrumentation.header.utils;
8+
package com.nr.agent.instrumentation.utils.header;
99

1010
public class HeaderType {
1111
public static final String NEWRELIC = "newrelic";
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*
66
*/
77

8-
package com.nr.agent.instrumentation.header.utils;
8+
package com.nr.agent.instrumentation.utils.header;
99

1010
import io.opentelemetry.api.trace.SpanContext;
1111

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
*
66
*/
77

8-
package com.nr.agent.instrumentation.header.utils;
8+
package com.nr.agent.instrumentation.utils.header;
99

1010
import java.util.regex.Matcher;
1111
import java.util.regex.Pattern;
1212

13-
import static com.nr.agent.instrumentation.header.utils.W3CTraceParentHeader.W3C_VERSION;
13+
import static com.nr.agent.instrumentation.utils.header.W3CTraceParentHeader.W3C_VERSION;
1414
import static java.util.regex.Pattern.compile;
1515

1616
public class W3CTraceParentValidator {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
*
3+
* * Copyright 2025 New Relic Corporation. All rights reserved.
4+
* * SPDX-License-Identifier: Apache-2.0
5+
*
6+
*/
7+
8+
package com.nr.agent.instrumentation.utils.logs;
9+
10+
public class ExceptionUtil {
11+
public static final int MAX_STACK_SIZE = 300;
12+
13+
public static String getErrorStack(String errorStack) {
14+
if (validateString(errorStack) == null) {
15+
return null;
16+
}
17+
if (errorStack.length() <= MAX_STACK_SIZE) {
18+
return errorStack;
19+
}
20+
return errorStack.substring(0, MAX_STACK_SIZE);
21+
}
22+
23+
public static String getErrorMessage(String errorMessage) {
24+
return validateString(errorMessage);
25+
}
26+
27+
public static String getErrorClass(String errorClass) {
28+
return validateString(errorClass);
29+
}
30+
31+
public static String validateString(String s) {
32+
if (s == null || s.isEmpty()) {
33+
return null;
34+
}
35+
return s;
36+
}
37+
}

0 commit comments

Comments
 (0)