-
Notifications
You must be signed in to change notification settings - Fork 3
Opentelemetry support #450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
22db5ec
fix(gcp): map OpenTelemetry trace fields
viliket 33cb79f
Opentelemetry support
skjolber 9faea6c
Clone examples with otel
skjolber dd36a53
Add agent otel detector
skjolber 0b3f1fd
Autodetect otel agent, if not fall back to micrometer
skjolber 6ab32d3
Delete / adjust tests, attempt to add sampled flag
skjolber 80096d9
Adjust provider, add example, add some assertions
skjolber e8725e1
Adjust readme
skjolber 2183d09
More examples
skjolber 65e75b5
Merge branch 'main' of github.com:entur/cloud-logging into opentelemetry
skjolber 24480f0
More examples, azure improvements, better spring otel detection, ++
skjolber 7ca8d3a
Mark as deprecated
skjolber 8b24027
Merge branch 'main' into opentelemetry
skjolber 25474bb
Fix tests
skjolber 149ff00
Merge branch 'opentelemetry' of github.com:entur/cloud-logging into o…
skjolber e56a2d2
Potential fix for pull request finding
skjolber efee91c
Potential fix for pull request finding
skjolber c79fbdf
Remove unfinished micrometer sampled implementation
skjolber 885c150
Merge branch 'opentelemetry' of github.com:entur/cloud-logging into o…
skjolber 9a06b17
Fix trace_sampled MDC flag for micrometer+OTel starter (PR 451) (#452)
Copilot dbfb69b
feat(gcp): move TraceSampledMdcHandler into spring-boot-autoconfigure…
Copilot 351c6bd
feat(examples): add downstream self-call + trace ID propagation tests…
Copilot b2e04ff
feat(examples): add gcp-grpc-spring-otel-agent-example and gcp-grpc-s…
Copilot c0dd9b0
test: verify StackdriverLogstashEncoder selects correct MDC trace pro…
Copilot f1aaf2a
refactor: reuse main java/proto sources from base modules in *-otel-*…
Copilot 72ebf9c
Add workflow for publishing snapshots
skjolber e50c1e7
Merge branch 'opentelemetry' of github.com:entur/cloud-logging into o…
skjolber b17db79
Merge branch 'main' of github.com:entur/cloud-logging into opentelemetry
skjolber 9fb8628
Reuse NoOpenTelemetryAgentCondition (#460)
skjolber 8281161
Adjust publishing
skjolber bb1725c
Adjust publishing
skjolber df3c173
Opentelemetry: Restore project id in trace (#462)
skjolber 454c826
Potential fix for pull request finding
skjolber eeaa4f6
Potential fix for pull request finding
skjolber 982c89a
Fix AzureLogstashEncoder to traverse nested providers when replacing …
Copilot fd3d87d
Futureproof sampled parser
skjolber 6a75022
Merge branch 'main' into opentelemetry
skjolber a6958e4
Potential fix for pull request finding
skjolber e222400
Add AzureOpenTelemetryTraceMdcJsonProviderTest
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
...no/entur/logging/cloud/azure/logback/logstash/AzureOpenTelemetryTraceMdcJsonProvider.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| package no.entur.logging.cloud.azure.logback.logstash; | ||
|
|
||
| import ch.qos.logback.classic.spi.ILoggingEvent; | ||
| import net.logstash.logback.composite.AbstractJsonProvider; | ||
| import tools.jackson.core.JsonGenerator; | ||
|
|
||
| import java.lang.management.ManagementFactory; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * An MDC provider that maps OpenTelemetry trace fields to the special JSON fields | ||
| * recognized by Azure: https://docs.azure.cn/en-us/spring-apps/basic-standard/structured-app-log | ||
| * | ||
| */ | ||
| public class AzureOpenTelemetryTraceMdcJsonProvider extends AbstractJsonProvider<ILoggingEvent> { | ||
|
|
||
| public static final String OPENTELEMETRY_TRACE_ID_KEY = "trace_id"; | ||
| public static final String OPENTELEMETRY_SPAN_ID_KEY = "span_id"; | ||
|
|
||
| public static final String AZURE_TRACE_KEY = "traceId"; | ||
| public static final String AZURE_SPAN_ID_KEY = "spanId"; | ||
|
|
||
| @Override | ||
| public void writeTo(JsonGenerator generator, ILoggingEvent event) { | ||
| Map<String, String> mdcProperties = event.getMDCPropertyMap(); | ||
| if (mdcProperties == null || mdcProperties.isEmpty()) { | ||
| return; | ||
| } | ||
|
|
||
| // map OTel MDC keys to Azure trace fields; write all others as-is. | ||
| for (Map.Entry<String, String> entry : mdcProperties.entrySet()) { | ||
| String key = entry.getKey(); | ||
| if (key == null) continue; | ||
| String value = entry.getValue(); | ||
| if (value == null) continue; | ||
|
|
||
| switch (key) { | ||
| case OPENTELEMETRY_TRACE_ID_KEY -> generator.writeStringProperty(AZURE_TRACE_KEY, value); | ||
| case OPENTELEMETRY_SPAN_ID_KEY -> generator.writeStringProperty(AZURE_SPAN_ID_KEY, value); | ||
| default -> generator.writeStringProperty(key, value); | ||
|
skjolber marked this conversation as resolved.
|
||
| } | ||
| } | ||
| } | ||
|
|
||
| public static boolean isOtelAgent() { | ||
| // 1. Check direct JVM command-line arguments (-javaagent) | ||
| List<String> jvmArgs = ManagementFactory.getRuntimeMXBean().getInputArguments(); | ||
| for (String arg : jvmArgs) { | ||
| if (isOtelArgument(arg)) { | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| // 2. Backup check for environment variables that inject JVM arguments | ||
| String javaToolOptions = System.getenv("JAVA_TOOL_OPTIONS"); | ||
| if (javaToolOptions != null && isOtelArgument(javaToolOptions)) { | ||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| private static boolean isOtelArgument(String argument) { | ||
| String lowerArg = argument.toLowerCase(); | ||
| return lowerArg.contains("-javaagent:") && lowerArg.contains("opentelemetry"); | ||
|
skjolber marked this conversation as resolved.
|
||
| } | ||
|
|
||
| } | ||
106 changes: 106 additions & 0 deletions
106
...ntur/logging/cloud/azure/logback/logstash/AzureOpenTelemetryTraceMdcJsonProviderTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| package no.entur.logging.cloud.azure.logback.logstash; | ||
|
|
||
| import ch.qos.logback.classic.spi.ILoggingEvent; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.mockito.Mockito; | ||
| import tools.jackson.core.JsonGenerator; | ||
| import tools.jackson.core.json.JsonFactory; | ||
| import tools.jackson.databind.JsonNode; | ||
| import tools.jackson.databind.json.JsonMapper; | ||
|
|
||
| import java.io.StringWriter; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.Map; | ||
|
|
||
| import static com.google.common.truth.Truth.assertThat; | ||
|
|
||
| public class AzureOpenTelemetryTraceMdcJsonProviderTest { | ||
|
|
||
| private static final JsonMapper MAPPER = JsonMapper.builder().build(); | ||
|
|
||
| @Test | ||
| void writeTo_openTelemetryTraceFields_mappedToAzureFields() throws Exception { | ||
| Map<String, String> mdc = new LinkedHashMap<>(); | ||
| mdc.put(AzureOpenTelemetryTraceMdcJsonProvider.OPENTELEMETRY_TRACE_ID_KEY, "06796866738c859f2f19b7cfb3214824"); | ||
| mdc.put(AzureOpenTelemetryTraceMdcJsonProvider.OPENTELEMETRY_SPAN_ID_KEY, "000000000000004a"); | ||
|
|
||
| JsonNode root = write(mdc); | ||
|
|
||
| assertThat(root.get(AzureOpenTelemetryTraceMdcJsonProvider.AZURE_TRACE_KEY).asText()) | ||
| .isEqualTo("06796866738c859f2f19b7cfb3214824"); | ||
| assertThat(root.get(AzureOpenTelemetryTraceMdcJsonProvider.AZURE_SPAN_ID_KEY).asText()) | ||
| .isEqualTo("000000000000004a"); | ||
| assertThat(root.has(AzureOpenTelemetryTraceMdcJsonProvider.OPENTELEMETRY_TRACE_ID_KEY)).isFalse(); | ||
| assertThat(root.has(AzureOpenTelemetryTraceMdcJsonProvider.OPENTELEMETRY_SPAN_ID_KEY)).isFalse(); | ||
| } | ||
|
|
||
| @Test | ||
| void writeTo_unrelatedMdcFields_preserved() throws Exception { | ||
| Map<String, String> mdc = new LinkedHashMap<>(); | ||
| mdc.put(AzureOpenTelemetryTraceMdcJsonProvider.OPENTELEMETRY_TRACE_ID_KEY, "abc"); | ||
| mdc.put("correlationId", "xyz123"); | ||
| mdc.put("userId", "user42"); | ||
|
|
||
| JsonNode root = write(mdc); | ||
|
|
||
| assertThat(root.get("correlationId").asText()).isEqualTo("xyz123"); | ||
| assertThat(root.get("userId").asText()).isEqualTo("user42"); | ||
| } | ||
|
|
||
| @Test | ||
| void writeTo_collisionBetweenOtelAndAzureKey_otelKeyMappedFirst() throws Exception { | ||
| // When MDC contains both the OTel key (trace_id) and the Azure target key (traceId), | ||
| // the OTel key is translated to traceId and appears in the output. | ||
| Map<String, String> mdc = new LinkedHashMap<>(); | ||
| mdc.put(AzureOpenTelemetryTraceMdcJsonProvider.OPENTELEMETRY_TRACE_ID_KEY, "otel-trace-value"); | ||
| mdc.put(AzureOpenTelemetryTraceMdcJsonProvider.AZURE_TRACE_KEY, "existing-azure-trace"); | ||
|
|
||
| JsonNode root = write(mdc); | ||
|
|
||
| // The OTel trace_id is mapped to traceId; readTree retains the last value on duplicate keys. | ||
| assertThat(root.get(AzureOpenTelemetryTraceMdcJsonProvider.AZURE_TRACE_KEY)).isNotNull(); | ||
| assertThat(root.has(AzureOpenTelemetryTraceMdcJsonProvider.OPENTELEMETRY_TRACE_ID_KEY)).isFalse(); | ||
| } | ||
|
|
||
| @Test | ||
| void writeTo_emptyMdc_writesNothing() throws Exception { | ||
| JsonNode root = write(new LinkedHashMap<>()); | ||
| assertThat(root.size()).isEqualTo(0); | ||
| } | ||
|
|
||
| @Test | ||
| void writeTo_nullMdc_writesNothing() throws Exception { | ||
| AzureOpenTelemetryTraceMdcJsonProvider provider = new AzureOpenTelemetryTraceMdcJsonProvider(); | ||
| ILoggingEvent event = Mockito.mock(ILoggingEvent.class); | ||
| Mockito.when(event.getMDCPropertyMap()).thenReturn(null); | ||
|
|
||
| StringWriter stringWriter = new StringWriter(); | ||
| JsonFactory factory = new JsonFactory(); | ||
| try (JsonGenerator generator = factory.createGenerator(stringWriter)) { | ||
| generator.writeStartObject(); | ||
| provider.writeTo(generator, event); | ||
| generator.writeEndObject(); | ||
| } | ||
| JsonNode root = MAPPER.readTree(stringWriter.toString()); | ||
| assertThat(root.size()).isEqualTo(0); | ||
| } | ||
|
|
||
| private static JsonNode write(Map<String, String> mdcMap) throws Exception { | ||
| return MAPPER.readTree(writeRaw(mdcMap)); | ||
| } | ||
|
|
||
| private static String writeRaw(Map<String, String> mdcMap) throws Exception { | ||
| AzureOpenTelemetryTraceMdcJsonProvider provider = new AzureOpenTelemetryTraceMdcJsonProvider(); | ||
| ILoggingEvent event = Mockito.mock(ILoggingEvent.class); | ||
| Mockito.when(event.getMDCPropertyMap()).thenReturn(mdcMap); | ||
|
|
||
| StringWriter stringWriter = new StringWriter(); | ||
| JsonFactory factory = new JsonFactory(); | ||
| try (JsonGenerator generator = factory.createGenerator(stringWriter)) { | ||
| generator.writeStartObject(); | ||
| provider.writeTo(generator, event); | ||
| generator.writeEndObject(); | ||
| } | ||
| return stringWriter.toString(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # gcp-grpc-spring-otel-agent-example | ||
| Simple GRPC service example with OpenTelemetry Java agent. | ||
|
|
||
| This emulates the deployed application (i.e. machine-readable JSON). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| plugins { | ||
| id 'org.springframework.boot' version '4.1.0' | ||
| id "com.google.protobuf" version "0.10.0" | ||
| } | ||
|
|
||
| configurations { | ||
| otelAgent | ||
| } | ||
|
|
||
| test { | ||
| useJUnitPlatform { | ||
| includeEngines 'junit-jupiter' | ||
| } | ||
|
|
||
| doFirst { | ||
| jvmArgs "-javaagent:${configurations.otelAgent.singleFile}" | ||
| } | ||
|
|
||
| systemProperty 'otel.traces.exporter', 'logging' | ||
| systemProperty 'otel.metrics.exporter', 'none' | ||
| systemProperty 'otel.logs.exporter', 'none' | ||
| systemProperty 'otel.service.name', 'junit5-tests' | ||
|
|
||
| systemProperty 'otel.instrumentation.http.server.exclude-paths', '/actuator/**' | ||
| } | ||
|
|
||
| dependencies { | ||
| otelAgent "io.opentelemetry.javaagent:opentelemetry-javaagent:2.30.0" | ||
|
|
||
| implementation project(':on-demand:on-demand-spring-boot-starter-grpc') | ||
| implementation project(':gcp:spring-boot-starter-gcp-grpc-spring') | ||
| implementation project(':gcp:request-response-spring-boot-starter-gcp-grpc-spring') | ||
| implementation project(':trace:server:correlation-id-trace-grpc-netty') | ||
| implementation project(':request-response:request-response-spring-boot-autoconfigure-grpc-spring') | ||
|
|
||
| implementation project(':trace:mdc-context-grpc-netty') | ||
|
|
||
| implementation("io.grpc:grpc-api:$grpcVersion") | ||
| implementation("io.grpc:grpc-core:$grpcVersion") | ||
| implementation("io.grpc:grpc-context:$grpcVersion") | ||
| implementation("io.grpc:grpc-stub:$grpcVersion") | ||
| //implementation("io.grpc:grpc-inprocess:$grpcVersion") | ||
| implementation("io.grpc:grpc-services:$grpcVersion") | ||
| implementation("io.grpc:grpc-netty:$grpcVersion") | ||
| implementation("io.grpc:grpc-util:$grpcVersion") | ||
| implementation("org.springframework.boot:spring-boot-starter") | ||
|
|
||
| testImplementation project(":gcp:spring-boot-starter-gcp-grpc-spring-test") | ||
| testImplementation project(':gcp:request-response-spring-boot-starter-gcp-grpc-spring-test') | ||
| testImplementation("org.springframework.boot:spring-boot-starter-test") | ||
|
|
||
| // added due to grpc plugin dependency resolution problem | ||
| testImplementation "org.junit.platform:junit-platform-launcher" | ||
|
|
||
| testImplementation ("com.google.truth:truth:${googleTruthVersion}") | ||
| testImplementation ("com.google.truth.extensions:truth-java8-extension:${googleTruthVersion}") | ||
|
|
||
| testImplementation project(":test:test-logback-junit") | ||
| } | ||
|
|
||
| sourceSets { | ||
| main { | ||
| java { | ||
| srcDirs '../gcp-grpc-spring-without-test-artifacts-example/src/main/java' | ||
| } | ||
| proto { | ||
| srcDirs '../gcp-grpc-spring-without-test-artifacts-example/src/main/proto' | ||
| } | ||
| resources { | ||
| srcDirs '../gcp-grpc-spring-without-test-artifacts-example/src/main/resources' | ||
| } | ||
| } | ||
| test.java.srcDirs += "${protobuf.generatedFilesBaseDir}/test/java" | ||
| test.java.srcDirs += "${protobuf.generatedFilesBaseDir}/test/grpc" | ||
| } | ||
|
|
||
| tasks.compileTestJava { dependsOn("generateTestProto") } | ||
|
|
||
| protobuf { | ||
| protoc { | ||
| artifact = "com.google.protobuf:protoc:${grpcProtobufVersion}" | ||
| } | ||
| plugins { | ||
| grpc { | ||
| artifact = "io.grpc:protoc-gen-grpc-java:$grpcVersion" | ||
| } | ||
| } | ||
| generateProtoTasks { | ||
| all()*.plugins { | ||
| grpc {} | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.