From b4345a3474b3707183b84bc986b3d220764fc55e Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 11 Mar 2025 15:41:09 -0700 Subject: [PATCH 01/11] Add smoke test --- .../LogbackWithSpanExceptionServlet.java | 17 ++++++ .../smoketest/LogbackDisabledTest.java | 28 +++++++++ .../smoketest/LogbackLevelOffTest.java | 61 +++++++++++++++++++ .../level_off_applicationinsights.json | 14 +++++ 4 files changed, 120 insertions(+) create mode 100644 smoke-tests/apps/Logback/src/main/java/com/microsoft/applicationinsights/smoketestapp/LogbackWithSpanExceptionServlet.java create mode 100644 smoke-tests/apps/Logback/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/LogbackLevelOffTest.java create mode 100644 smoke-tests/apps/Logback/src/smokeTest/resources/level_off_applicationinsights.json diff --git a/smoke-tests/apps/Logback/src/main/java/com/microsoft/applicationinsights/smoketestapp/LogbackWithSpanExceptionServlet.java b/smoke-tests/apps/Logback/src/main/java/com/microsoft/applicationinsights/smoketestapp/LogbackWithSpanExceptionServlet.java new file mode 100644 index 00000000000..53c2560af4a --- /dev/null +++ b/smoke-tests/apps/Logback/src/main/java/com/microsoft/applicationinsights/smoketestapp/LogbackWithSpanExceptionServlet.java @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.applicationinsights.smoketestapp; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/testWithSpanException") +public class LogbackWithSpanExceptionServlet extends HttpServlet { + + protected void doGet(HttpServletRequest request, HttpServletResponse response) { + throw new RuntimeException("Test Exception"); + } +} diff --git a/smoke-tests/apps/Logback/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/LogbackDisabledTest.java b/smoke-tests/apps/Logback/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/LogbackDisabledTest.java index 959dee6ffc5..5ec40bb3e3f 100644 --- a/smoke-tests/apps/Logback/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/LogbackDisabledTest.java +++ b/smoke-tests/apps/Logback/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/LogbackDisabledTest.java @@ -8,6 +8,7 @@ import com.microsoft.applicationinsights.smoketest.schemav2.Data; import com.microsoft.applicationinsights.smoketest.schemav2.Envelope; +import com.microsoft.applicationinsights.smoketest.schemav2.ExceptionData; import com.microsoft.applicationinsights.smoketest.schemav2.RequestData; import java.util.List; import org.junit.jupiter.api.Test; @@ -30,4 +31,31 @@ void testDisabled() throws Exception { assertThat(testing.mockedIngestion.getCountForType("MessageData")).isZero(); } + + @Test + @TargetUri("/testWithSpanException") + void testWithSpanException() throws Exception { + List rdList = testing.mockedIngestion.waitForItems("RequestData", 1); + + Envelope rdEnvelope = rdList.get(0); + RequestData rd = (RequestData) ((Data) rdEnvelope.getData()).getBaseData(); + assertThat(rd.getName()).isEqualTo("GET /Logback/testWithSpanException"); + + assertThat(testing.mockedIngestion.getCountForType("MessageData")).isZero(); + + // check that span exception is still captured + String operationId = rdEnvelope.getTags().get("ai.operation.id"); + List edList = + testing.mockedIngestion.waitForItemsInOperation("ExceptionData", 1, operationId); + + Envelope edEnvelope = edList.get(0); + ExceptionData ed = (ExceptionData) ((Data) edEnvelope.getData()).getBaseData(); + + assertThat(ed.getExceptions().get(0).getTypeName()).isEqualTo("java.lang.RuntimeException"); + assertThat(ed.getExceptions().get(0).getMessage()).isEqualTo("Test Exception"); + assertThat(ed.getProperties()).isEmpty(); // this is not a logger-based exception + + SmokeTestExtension.assertParentChild( + rd, rdEnvelope, edEnvelope, "GET /Logback/testWithSpanException"); + } } diff --git a/smoke-tests/apps/Logback/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/LogbackLevelOffTest.java b/smoke-tests/apps/Logback/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/LogbackLevelOffTest.java new file mode 100644 index 00000000000..cea6ed72db2 --- /dev/null +++ b/smoke-tests/apps/Logback/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/LogbackLevelOffTest.java @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.applicationinsights.smoketest; + +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_8; +import static org.assertj.core.api.Assertions.assertThat; + +import com.microsoft.applicationinsights.smoketest.schemav2.Data; +import com.microsoft.applicationinsights.smoketest.schemav2.Envelope; +import com.microsoft.applicationinsights.smoketest.schemav2.ExceptionData; +import com.microsoft.applicationinsights.smoketest.schemav2.RequestData; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +@Environment(TOMCAT_8_JAVA_8) +@UseAgent("level_off_applicationinsights.json") +class LogbackLevelOffTest { + + @RegisterExtension static final SmokeTestExtension testing = SmokeTestExtension.create(); + + @Test + @TargetUri("/test") + void testDisabled() throws Exception { + List rdList = testing.mockedIngestion.waitForItems("RequestData", 1); + + Envelope rdEnvelope = rdList.get(0); + RequestData rd = (RequestData) ((Data) rdEnvelope.getData()).getBaseData(); + assertThat(rd.getName()).isEqualTo("GET /Logback/test"); + + assertThat(testing.mockedIngestion.getCountForType("MessageData")).isZero(); + } + + @Test + @TargetUri("/testWithSpanException") + void testWithSpanException() throws Exception { + List rdList = testing.mockedIngestion.waitForItems("RequestData", 1); + + Envelope rdEnvelope = rdList.get(0); + RequestData rd = (RequestData) ((Data) rdEnvelope.getData()).getBaseData(); + assertThat(rd.getName()).isEqualTo("GET /Logback/testWithSpanException"); + + assertThat(testing.mockedIngestion.getCountForType("MessageData")).isZero(); + + // check that span exception is still captured + String operationId = rdEnvelope.getTags().get("ai.operation.id"); + List edList = + testing.mockedIngestion.waitForItemsInOperation("ExceptionData", 1, operationId); + + Envelope edEnvelope = edList.get(0); + ExceptionData ed = (ExceptionData) ((Data) edEnvelope.getData()).getBaseData(); + + assertThat(ed.getExceptions().get(0).getTypeName()).isEqualTo("java.lang.RuntimeException"); + assertThat(ed.getExceptions().get(0).getMessage()).isEqualTo("Test Exception"); + assertThat(ed.getProperties()).isEmpty(); // this is not a logger-based exception + + SmokeTestExtension.assertParentChild( + rd, rdEnvelope, edEnvelope, "GET /Logback/testWithSpanException"); + } +} diff --git a/smoke-tests/apps/Logback/src/smokeTest/resources/level_off_applicationinsights.json b/smoke-tests/apps/Logback/src/smokeTest/resources/level_off_applicationinsights.json new file mode 100644 index 00000000000..c19c1fca9dd --- /dev/null +++ b/smoke-tests/apps/Logback/src/smokeTest/resources/level_off_applicationinsights.json @@ -0,0 +1,14 @@ +{ + "role": { + "name": "testrolename", + "instance": "testroleinstance" + }, + "sampling": { + "percentage": 100 + }, + "instrumentation": { + "logging": { + "level": "off" + } + } +} From 90f4ecef19a3e7d2cfae9457452c8a7873cc9870 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 11 Mar 2025 20:30:18 -0700 Subject: [PATCH 02/11] Set exception logged only when it really is emitted --- .../internal/exporter/AgentLogExporter.java | 63 +------ .../AzureMonitorLogFilteringProcessor.java | 176 ++++++++++++++++++ .../init/AzureMonitorLogProcessor.java | 88 +-------- .../internal/init/RuntimeConfigurator.java | 13 +- .../agent/internal/init/SecondEntryPoint.java | 52 +++--- 5 files changed, 212 insertions(+), 180 deletions(-) create mode 100644 agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/exporter/AgentLogExporter.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/exporter/AgentLogExporter.java index e0d45db63d4..9af29f1904f 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/exporter/AgentLogExporter.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/exporter/AgentLogExporter.java @@ -10,23 +10,16 @@ import com.azure.monitor.opentelemetry.autoconfigure.implementation.logging.OperationLogger; import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryItem; import com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.QuickPulse; -import com.microsoft.applicationinsights.agent.internal.configuration.Configuration.SamplingOverride; -import com.microsoft.applicationinsights.agent.internal.sampling.AiFixedPercentageSampler; -import com.microsoft.applicationinsights.agent.internal.sampling.SamplingOverrides; import com.microsoft.applicationinsights.agent.internal.telemetry.BatchItemProcessor; import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryClient; import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryObservers; import io.opentelemetry.api.logs.LoggerProvider; -import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.javaagent.bootstrap.CallDepth; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.logs.export.LogRecordExporter; -import io.opentelemetry.sdk.trace.samplers.SamplingDecision; -import io.opentelemetry.sdk.trace.samplers.SamplingResult; import io.opentelemetry.semconv.ExceptionAttributes; import java.util.Collection; -import java.util.List; import java.util.function.Consumer; import javax.annotation.Nullable; import org.slf4j.Logger; @@ -39,24 +32,13 @@ public class AgentLogExporter implements LogRecordExporter { private static final OperationLogger exportingLogLogger = new OperationLogger(AgentLogExporter.class, "Exporting log"); - // TODO (trask) could implement this in a filtering LogExporter instead - private volatile int severityThreshold; - - private final SamplingOverrides logSamplingOverrides; - private final SamplingOverrides exceptionSamplingOverrides; private final LogDataMapper mapper; private final Consumer telemetryItemConsumer; public AgentLogExporter( - int severityThreshold, - List logSamplingOverrides, - List exceptionSamplingOverrides, LogDataMapper mapper, @Nullable QuickPulse quickPulse, BatchItemProcessor batchItemProcessor) { - this.severityThreshold = severityThreshold; - this.logSamplingOverrides = new SamplingOverrides(logSamplingOverrides); - this.exceptionSamplingOverrides = new SamplingOverrides(exceptionSamplingOverrides); this.mapper = mapper; telemetryItemConsumer = telemetryItem -> { @@ -70,10 +52,6 @@ public AgentLogExporter( }; } - public void setSeverityThreshold(int severityThreshold) { - this.severityThreshold = severityThreshold; - } - @Override public CompletableResultCode export(Collection logs) { // incrementing CallDepth for LoggerProvider causes the OpenTelemetry Java agent logging @@ -107,47 +85,12 @@ private CompletableResultCode internalExport(Collection logs) { private void internalExport(LogRecordData log) { try { - int severityNumber = log.getSeverity().getSeverityNumber(); - if (severityNumber < severityThreshold) { - return; - } + logger.debug("exporting log: {}", log); String stack = log.getAttributes().get(ExceptionAttributes.EXCEPTION_STACKTRACE); + Double sampleRate = log.getAttributes().get(AiSemanticAttributes.SAMPLE_RATE); - SamplingOverrides samplingOverrides = - stack != null ? exceptionSamplingOverrides : logSamplingOverrides; - - SpanContext spanContext = log.getSpanContext(); - Double parentSpanSampleRate = log.getAttributes().get(AiSemanticAttributes.SAMPLE_RATE); - - AiFixedPercentageSampler sampler = samplingOverrides.getOverride(log.getAttributes()); - - boolean hasSamplingOverride = sampler != null; - - if (!hasSamplingOverride - && spanContext.isValid() - && !spanContext.getTraceFlags().isSampled()) { - // if there is no sampling override, and the log is part of an unsampled trace, - // then don't capture it - return; - } - - Double sampleRate = null; - if (hasSamplingOverride) { - SamplingResult samplingResult = sampler.shouldSampleLog(spanContext, parentSpanSampleRate); - if (samplingResult.getDecision() != SamplingDecision.RECORD_AND_SAMPLE) { - return; - } - sampleRate = samplingResult.getAttributes().get(AiSemanticAttributes.SAMPLE_RATE); - } - - if (sampleRate == null) { - sampleRate = parentSpanSampleRate; - } - - logger.debug("exporting log: {}", log); - - // TODO (trask) no longer need to check AiSemanticAttributes.SAMPLE_RATE in map() method + // TODO (trask) get stack and sampleRate inside map() method instead of passing into TelemetryItem telemetryItem = mapper.map(log, stack, sampleRate); telemetryItemConsumer.accept(telemetryItem); diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java new file mode 100644 index 00000000000..5af46d69ff3 --- /dev/null +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.applicationinsights.agent.internal.init; + +import com.azure.core.util.logging.ClientLogger; +import com.azure.monitor.opentelemetry.autoconfigure.implementation.AiSemanticAttributes; +import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; +import com.microsoft.applicationinsights.agent.internal.sampling.AiFixedPercentageSampler; +import com.microsoft.applicationinsights.agent.internal.sampling.SamplingOverrides; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan; +import io.opentelemetry.sdk.internal.AttributesMap; +import io.opentelemetry.sdk.logs.LogRecordProcessor; +import io.opentelemetry.sdk.logs.ReadWriteLogRecord; +import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; +import io.opentelemetry.sdk.trace.ReadableSpan; +import io.opentelemetry.sdk.trace.samplers.SamplingDecision; +import io.opentelemetry.sdk.trace.samplers.SamplingResult; +import io.opentelemetry.semconv.ExceptionAttributes; +import java.lang.reflect.Field; +import java.util.List; +import javax.annotation.Nullable; + +public class AzureMonitorLogFilteringProcessor implements LogRecordProcessor { + + private static final ClientLogger logger = new ClientLogger(AzureMonitorLogProcessor.class); + private static final Field lockField; + private static final Field attributesMapField; + + static { + Class sdkReadWriteLogRecordClass = getSdkReadWriteLogRecordClass(); + lockField = getLockField(sdkReadWriteLogRecordClass); + attributesMapField = getAttributesMapField(sdkReadWriteLogRecordClass); + } + + private final SamplingOverrides logSamplingOverrides; + private final SamplingOverrides exceptionSamplingOverrides; + private final BatchLogRecordProcessor batchLogRecordProcessor; + + private volatile int severityThreshold; + + public AzureMonitorLogFilteringProcessor( + List logSamplingOverrides, + List exceptionSamplingOverrides, + BatchLogRecordProcessor batchLogRecordProcessor, + int severityThreshold) { + + this.severityThreshold = severityThreshold; + this.logSamplingOverrides = new SamplingOverrides(logSamplingOverrides); + this.exceptionSamplingOverrides = new SamplingOverrides(exceptionSamplingOverrides); + this.batchLogRecordProcessor = batchLogRecordProcessor; + this.severityThreshold = severityThreshold; + } + + public void setSeverityThreshold(int severityThreshold) { + this.severityThreshold = severityThreshold; + } + + @Override + public void onEmit(Context context, ReadWriteLogRecord logRecord) { + + int severityNumber = logRecord.getSeverity().getSeverityNumber(); + if (severityNumber < severityThreshold) { + // quick return + return; + } + + Double parentSpanSampleRate = null; + Span currentSpan = Span.fromContext(context); + if (currentSpan instanceof ReadableSpan) { + ReadableSpan readableSpan = (ReadableSpan) currentSpan; + parentSpanSampleRate = readableSpan.getAttribute(AiSemanticAttributes.SAMPLE_RATE); + } + + // deal with sampling synchronously so that we only call setAttributeExceptionLogged() + // when we know we are emitting the exception (span sampling happens synchronously as well) + + String stack = logRecord.getAttribute(ExceptionAttributes.EXCEPTION_STACKTRACE); + + SamplingOverrides samplingOverrides = + stack != null ? exceptionSamplingOverrides : logSamplingOverrides; + + SpanContext spanContext = logRecord.getSpanContext(); + + AiFixedPercentageSampler sampler = samplingOverrides.getOverride(logRecord.getAttributes()); + + boolean hasSamplingOverride = sampler != null; + + if (!hasSamplingOverride && spanContext.isValid() && !spanContext.getTraceFlags().isSampled()) { + // if there is no sampling override, and the log is part of an unsampled trace, + // then don't capture it + return; + } + + Double sampleRate = null; + if (hasSamplingOverride) { + SamplingResult samplingResult = sampler.shouldSampleLog(spanContext, parentSpanSampleRate); + if (samplingResult.getDecision() != SamplingDecision.RECORD_AND_SAMPLE) { + return; + } + sampleRate = samplingResult.getAttributes().get(AiSemanticAttributes.SAMPLE_RATE); + } + + if (sampleRate == null) { + sampleRate = parentSpanSampleRate; + } + + if (sampleRate != null) { + logRecord.setAttribute(AiSemanticAttributes.SAMPLE_RATE, sampleRate); + } + + setAttributeExceptionLogged(LocalRootSpan.fromContext(context), logRecord); + + batchLogRecordProcessor.onEmit(context, logRecord); + } + + @Nullable + private static Class getSdkReadWriteLogRecordClass() { + try { + return Class.forName("io.opentelemetry.sdk.logs.SdkReadWriteLogRecord"); + } catch (ClassNotFoundException e) { + return null; + } + } + + @Nullable + private static Field getLockField(Class sdkReadWriteLogRecordClass) { + if (sdkReadWriteLogRecordClass == null) { + return null; + } + try { + Field lockField = sdkReadWriteLogRecordClass.getDeclaredField("lock"); + lockField.setAccessible(true); + return lockField; + } catch (NoSuchFieldException e) { + return null; + } + } + + @Nullable + private static Field getAttributesMapField(Class sdkReadWriteLogRecordClass) { + if (sdkReadWriteLogRecordClass == null) { + return null; + } + try { + Field attributesMapField = sdkReadWriteLogRecordClass.getDeclaredField("attributes"); + attributesMapField.setAccessible(true); + return attributesMapField; + } catch (NoSuchFieldException e) { + return null; + } + } + + private static void setAttributeExceptionLogged(Span span, ReadWriteLogRecord logRecord) { + if (lockField == null || attributesMapField == null) { + return; + } + String stacktrace = null; + try { + synchronized (lockField) { + // TODO add `getAttribute()` to `ReadWriteLogRecord` upstream + stacktrace = + ((AttributesMap) attributesMapField.get(logRecord)) + .get(ExceptionAttributes.EXCEPTION_STACKTRACE); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + if (stacktrace != null) { + span.setAttribute(AiSemanticAttributes.LOGGED_EXCEPTION, stacktrace); + } + } +} diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogProcessor.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogProcessor.java index 3aa66293dc7..6bb42702f24 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogProcessor.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogProcessor.java @@ -3,103 +3,23 @@ package com.microsoft.applicationinsights.agent.internal.init; -import com.azure.core.util.logging.ClientLogger; import com.azure.monitor.opentelemetry.autoconfigure.implementation.AiSemanticAttributes; import com.azure.monitor.opentelemetry.autoconfigure.implementation.OperationNames; import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan; -import io.opentelemetry.sdk.internal.AttributesMap; import io.opentelemetry.sdk.logs.LogRecordProcessor; import io.opentelemetry.sdk.logs.ReadWriteLogRecord; import io.opentelemetry.sdk.trace.ReadableSpan; -import io.opentelemetry.semconv.ExceptionAttributes; -import java.lang.reflect.Field; -import javax.annotation.Nullable; public class AzureMonitorLogProcessor implements LogRecordProcessor { - private static final ClientLogger logger = new ClientLogger(AzureMonitorLogProcessor.class); - private static final Field lockField; - private static final Field attributesMapField; - - static { - Class sdkReadWriteLogRecordClass = getSdkReadWriteLogRecordClass(); - lockField = getLockField(sdkReadWriteLogRecordClass); - attributesMapField = getAttributesMapField(sdkReadWriteLogRecordClass); - } - @Override public void onEmit(Context context, ReadWriteLogRecord logRecord) { Span currentSpan = Span.fromContext(context); - if (!(currentSpan instanceof ReadableSpan)) { - return; - } - setAttributeExceptionLogged(LocalRootSpan.fromContext(context), logRecord); - - ReadableSpan readableSpan = (ReadableSpan) currentSpan; - logRecord.setAttribute( - AiSemanticAttributes.OPERATION_NAME, OperationNames.getOperationName(readableSpan)); - Double sampleRate = readableSpan.getAttribute(AiSemanticAttributes.SAMPLE_RATE); - if (sampleRate != null) { - logRecord.setAttribute(AiSemanticAttributes.SAMPLE_RATE, sampleRate); - } - } - - @Nullable - private static Class getSdkReadWriteLogRecordClass() { - try { - return Class.forName("io.opentelemetry.sdk.logs.SdkReadWriteLogRecord"); - } catch (ClassNotFoundException e) { - return null; - } - } - - @Nullable - private static Field getLockField(Class sdkReadWriteLogRecordClass) { - if (sdkReadWriteLogRecordClass == null) { - return null; - } - try { - Field lockField = sdkReadWriteLogRecordClass.getDeclaredField("lock"); - lockField.setAccessible(true); - return lockField; - } catch (NoSuchFieldException e) { - return null; - } - } - - @Nullable - private static Field getAttributesMapField(Class sdkReadWriteLogRecordClass) { - if (sdkReadWriteLogRecordClass == null) { - return null; - } - try { - Field attributesMapField = sdkReadWriteLogRecordClass.getDeclaredField("attributes"); - attributesMapField.setAccessible(true); - return attributesMapField; - } catch (NoSuchFieldException e) { - return null; - } - } - - private static void setAttributeExceptionLogged(Span span, ReadWriteLogRecord logRecord) { - if (lockField == null || attributesMapField == null) { - return; - } - String stacktrace = null; - try { - synchronized (lockField) { - // TODO add `getAttribute()` to `ReadWriteLogRecord` upstream - stacktrace = - ((AttributesMap) attributesMapField.get(logRecord)) - .get(ExceptionAttributes.EXCEPTION_STACKTRACE); - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - if (stacktrace != null) { - span.setAttribute(AiSemanticAttributes.LOGGED_EXCEPTION, stacktrace); + if (currentSpan instanceof ReadableSpan) { + ReadableSpan readableSpan = (ReadableSpan) currentSpan; + logRecord.setAttribute( + AiSemanticAttributes.OPERATION_NAME, OperationNames.getOperationName(readableSpan)); } } } diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/RuntimeConfigurator.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/RuntimeConfigurator.java index 5f41773edf0..2109d0cb9cb 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/RuntimeConfigurator.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/RuntimeConfigurator.java @@ -12,7 +12,6 @@ import com.microsoft.applicationinsights.agent.internal.classicsdk.BytecodeUtilImpl; import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; import com.microsoft.applicationinsights.agent.internal.configuration.SnippetConfiguration; -import com.microsoft.applicationinsights.agent.internal.exporter.AgentLogExporter; import com.microsoft.applicationinsights.agent.internal.legacyheaders.DelegatingPropagator; import com.microsoft.applicationinsights.agent.internal.profiler.ProfilingInitializer; import com.microsoft.applicationinsights.agent.internal.sampling.DelegatingSampler; @@ -34,7 +33,7 @@ public class RuntimeConfigurator { private static final Logger logger = LoggerFactory.getLogger(RuntimeConfigurator.class); private final TelemetryClient telemetryClient; - private final Supplier agentLogExporter; + private final Supplier logFilteringProcessor; private final Configuration initialConfig; private volatile RuntimeConfiguration currentConfig; private final Consumer> heartbeatTelemetryItemsConsumer; @@ -45,12 +44,12 @@ public class RuntimeConfigurator { RuntimeConfigurator( TelemetryClient telemetryClient, - Supplier agentLogExporter, + Supplier logFilteringProcessor, Configuration initialConfig, Consumer> heartbeatTelemetryItemConsumer, File tempDir) { this.telemetryClient = telemetryClient; - this.agentLogExporter = agentLogExporter; + this.logFilteringProcessor = logFilteringProcessor; this.initialConfig = initialConfig; currentConfig = captureInitialConfig(initialConfig); this.heartbeatTelemetryItemsConsumer = heartbeatTelemetryItemConsumer; @@ -232,9 +231,9 @@ private void updateRoleInstance(@Nullable String roleInstance) { private void updateInstrumentationLoggingLevel(String instrumentationLoggingLevel) { if (instrumentationLoggingLevel != null) { - AgentLogExporter exporter = agentLogExporter.get(); - if (exporter != null) { - exporter.setSeverityThreshold( + AzureMonitorLogFilteringProcessor filteringProcessor = logFilteringProcessor.get(); + if (filteringProcessor != null) { + filteringProcessor.setSeverityThreshold( Configuration.LoggingInstrumentation.getSeverityThreshold(instrumentationLoggingLevel)); } } diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java index f2a0864f40f..12fb8dda68d 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java @@ -58,6 +58,7 @@ import io.opentelemetry.sdk.autoconfigure.spi.internal.AutoConfigureListener; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.logs.LogRecordProcessor; +import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; import io.opentelemetry.sdk.logs.export.LogRecordExporter; import io.opentelemetry.sdk.metrics.Aggregation; import io.opentelemetry.sdk.metrics.InstrumentSelector; @@ -77,7 +78,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -90,7 +90,7 @@ public class SecondEntryPoint new ClientLogger("com.microsoft.applicationinsights.agent"); private static File tempDir; - @Nullable private static AgentLogExporter agentLogExporter; + @Nullable private static AzureMonitorLogFilteringProcessor logFilteringProcessor; static File getTempDir() { return tempDir; @@ -170,7 +170,7 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { RuntimeConfigurator runtimeConfigurator = new RuntimeConfigurator( telemetryClient, - () -> agentLogExporter, + () -> logFilteringProcessor, configuration, heartbeatTelemetryItemConsumer, tempDir); @@ -238,8 +238,6 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { } telemetryClient.setQuickPulse(quickPulse); - AtomicBoolean firstLogRecordProcessor = new AtomicBoolean(true); - autoConfiguration .addPropertiesSupplier( () -> { @@ -264,13 +262,27 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { .addPropertiesCustomizer(new AiConfigCustomizer()) .addLogRecordProcessorCustomizer( (logRecordProcessor, configProperties) -> { - if (firstLogRecordProcessor.getAndSet(false)) { - // hack to run our log record processors first, before any other log processors - // (in particular before the batch log processor which performs the export) - // see https://github.com/open-telemetry/opentelemetry-java/issues/6599 + if (logRecordProcessor instanceof BatchLogRecordProcessor) { List logRecordProcessors = getLogRecordProcessors(configuration); - logRecordProcessors.add(logRecordProcessor); + List logSamplingOverrides = + configuration.sampling.overrides.stream() + .filter(override -> override.telemetryType == SamplingTelemetryType.TRACE) + .collect(Collectors.toList()); + List exceptionSamplingOverrides = + configuration.sampling.overrides.stream() + .filter( + override -> override.telemetryType == SamplingTelemetryType.EXCEPTION) + .collect(Collectors.toList()); + + logFilteringProcessor = + new AzureMonitorLogFilteringProcessor( + logSamplingOverrides, + exceptionSamplingOverrides, + (BatchLogRecordProcessor) logRecordProcessor, + configuration.instrumentation.logging.getSeverityThreshold()); + + logRecordProcessors.add(logFilteringProcessor); return LogRecordProcessor.composite( logRecordProcessors.toArray(new LogRecordProcessor[0])); } @@ -673,25 +685,7 @@ private static LogRecordExporter createLogExporter( ConfigurationBuilder.inAzureFunctionsWorker(System::getenv), telemetryClient::populateDefaults); - List logSamplingOverrides = - configuration.sampling.overrides.stream() - .filter(override -> override.telemetryType == SamplingTelemetryType.TRACE) - .collect(Collectors.toList()); - List exceptionSamplingOverrides = - configuration.sampling.overrides.stream() - .filter(override -> override.telemetryType == SamplingTelemetryType.EXCEPTION) - .collect(Collectors.toList()); - - agentLogExporter = - new AgentLogExporter( - configuration.instrumentation.logging.getSeverityThreshold(), - logSamplingOverrides, - exceptionSamplingOverrides, - mapper, - quickPulse, - telemetryClient.getGeneralBatchItemProcessor()); - - return agentLogExporter; + return new AgentLogExporter(mapper, quickPulse, telemetryClient.getGeneralBatchItemProcessor()); } private static LogRecordExporter wrapLogExporter( From 2a61aa1270bc92e8070378e9c8a867cb1cf086c6 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 12 Mar 2025 09:18:02 -0700 Subject: [PATCH 03/11] fix --- .../init/AzureMonitorLogFilteringProcessor.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java index 5af46d69ff3..a93b5bc34c7 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java @@ -12,6 +12,7 @@ import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan; +import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.internal.AttributesMap; import io.opentelemetry.sdk.logs.LogRecordProcessor; import io.opentelemetry.sdk.logs.ReadWriteLogRecord; @@ -117,6 +118,18 @@ public void onEmit(Context context, ReadWriteLogRecord logRecord) { batchLogRecordProcessor.onEmit(context, logRecord); } + public CompletableResultCode shutdown() { + return batchLogRecordProcessor.shutdown(); + } + + public CompletableResultCode forceFlush() { + return batchLogRecordProcessor.forceFlush(); + } + + public void close() { + batchLogRecordProcessor.close(); + } + @Nullable private static Class getSdkReadWriteLogRecordClass() { try { From decc8e9c47836e7ce31859977a12e5593e680f3f Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 12 Mar 2025 09:38:07 -0700 Subject: [PATCH 04/11] override --- .../agent/internal/init/AzureMonitorLogFilteringProcessor.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java index a93b5bc34c7..6ca3931420c 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java @@ -118,14 +118,17 @@ public void onEmit(Context context, ReadWriteLogRecord logRecord) { batchLogRecordProcessor.onEmit(context, logRecord); } + @Override public CompletableResultCode shutdown() { return batchLogRecordProcessor.shutdown(); } + @Override public CompletableResultCode forceFlush() { return batchLogRecordProcessor.forceFlush(); } + @Override public void close() { batchLogRecordProcessor.close(); } From 479a71318b88fe002ad78c034f7f93f49f993fb3 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 12 Mar 2025 11:29:41 -0700 Subject: [PATCH 05/11] build scans --- settings.gradle.kts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/settings.gradle.kts b/settings.gradle.kts index 6f8af6bb4ad..fd28f8efa1e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,9 +6,14 @@ pluginManagement { id("com.github.jk1.dependency-license-report") version "2.9" id("me.champeau.jmh") version "0.7.3" id("com.gradle.plugin-publish") version "1.3.1" + id("com.gradle.develocity") version "3.19.2" } } +plugins { + id("com.gradle.develocity") +} + dependencyResolutionManagement { repositories { mavenCentral() @@ -16,6 +21,14 @@ dependencyResolutionManagement { } } +develocity { + buildScan { + publishing.onlyIf { System.getenv("CI") != null } + termsOfUseUrl.set("https://gradle.com/help/legal-terms-of-use") + termsOfUseAgree.set("yes") + } +} + rootProject.name = "ApplicationInsights-Java" val buildNative = System.getProperty("ai.etw.native.build") != null && Os.isFamily(Os.FAMILY_WINDOWS) From 009e65ea56e931687b52f45eda85f2246b778f67 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 12 Mar 2025 12:27:40 -0700 Subject: [PATCH 06/11] upload test reports --- .github/workflows/build-common.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index 8f16c7973c9..5687a7de663 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -216,3 +216,15 @@ jobs: - name: Test run: ./gradlew ${{ matrix.module }}:smokeTest + + - name: Create unique artifact name + if: failure() + run: | + echo "UPLOAD_ARTIFACT_NAME=${{ matrix.module }}" | sed 's/:/-/g' >> $GITHUB_ENV + + - name: Upload smoke test reports + uses: actions/upload-artifact@v4 + if: failure() + with: + name: ${{ env.UPLOAD_ARTIFACT_NAME }} + path: '**/build/reports/tests/smokeTest/**/*' From 13e58d91ec0c4ef4b2bd77b663ab5b11b00ee64a Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 12 Mar 2025 19:33:48 -0700 Subject: [PATCH 07/11] fix --- .../agent/internal/init/SecondEntryPoint.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java index 12fb8dda68d..e0d9519b43b 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java @@ -334,7 +334,7 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { private static SpanExporter buildTraceExporter( Configuration configuration, TelemetryClient telemetryClient, QuickPulse quickPulse) { List exceptionSamplingOverrides = - configuration.preview.sampling.overrides.stream() + configuration.sampling.overrides.stream() .filter(override -> override.telemetryType == SamplingTelemetryType.EXCEPTION) .collect(Collectors.toList()); SpanExporter spanExporter = @@ -437,7 +437,7 @@ private static Set initStatsbeatFeatureSet(Configuration config) { if (config.preview.browserSdkLoader.enabled) { featureList.add(Feature.BROWSER_SDK_LOADER); } - if (!config.preview.sampling.overrides.isEmpty()) { + if (!config.sampling.overrides.isEmpty()) { featureList.add(Feature.SAMPLING); } if (config.preview.captureControllerSpans) { From 57a53b5db4598d22d76eb6789decabb573378657 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 17 Mar 2025 15:13:04 -0700 Subject: [PATCH 08/11] comment --- .../agent/internal/init/SecondEntryPoint.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java index e0d9519b43b..ba0d578f3ae 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java @@ -262,6 +262,10 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { .addPropertiesCustomizer(new AiConfigCustomizer()) .addLogRecordProcessorCustomizer( (logRecordProcessor, configProperties) -> { + // filtering log record processors need to be chained on front of the batch log record + // processor, hopefully log filtering will be better supported by OpenTelemetry SDK in + // the future + // (e.g. https://github.com/open-telemetry/opentelemetry-specification/pull/4439) if (logRecordProcessor instanceof BatchLogRecordProcessor) { List logRecordProcessors = getLogRecordProcessors(configuration); From d7c191581329e798b0fe797e0443a8c16eb61a04 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 17 Mar 2025 15:17:23 -0700 Subject: [PATCH 09/11] extract method --- .../AzureMonitorLogFilteringProcessor.java | 5 +-- .../agent/internal/init/SecondEntryPoint.java | 42 +++++++++++-------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java index 6ca3931420c..2f31977f2d7 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java @@ -16,7 +16,6 @@ import io.opentelemetry.sdk.internal.AttributesMap; import io.opentelemetry.sdk.logs.LogRecordProcessor; import io.opentelemetry.sdk.logs.ReadWriteLogRecord; -import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; import io.opentelemetry.sdk.trace.ReadableSpan; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import io.opentelemetry.sdk.trace.samplers.SamplingResult; @@ -39,14 +38,14 @@ public class AzureMonitorLogFilteringProcessor implements LogRecordProcessor { private final SamplingOverrides logSamplingOverrides; private final SamplingOverrides exceptionSamplingOverrides; - private final BatchLogRecordProcessor batchLogRecordProcessor; + private final LogRecordProcessor batchLogRecordProcessor; private volatile int severityThreshold; public AzureMonitorLogFilteringProcessor( List logSamplingOverrides, List exceptionSamplingOverrides, - BatchLogRecordProcessor batchLogRecordProcessor, + LogRecordProcessor batchLogRecordProcessor, int severityThreshold) { this.severityThreshold = severityThreshold; diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java index ba0d578f3ae..3b9ff699524 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java @@ -262,29 +262,16 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { .addPropertiesCustomizer(new AiConfigCustomizer()) .addLogRecordProcessorCustomizer( (logRecordProcessor, configProperties) -> { - // filtering log record processors need to be chained on front of the batch log record - // processor, hopefully log filtering will be better supported by OpenTelemetry SDK in - // the future - // (e.g. https://github.com/open-telemetry/opentelemetry-specification/pull/4439) if (logRecordProcessor instanceof BatchLogRecordProcessor) { List logRecordProcessors = getLogRecordProcessors(configuration); - List logSamplingOverrides = - configuration.sampling.overrides.stream() - .filter(override -> override.telemetryType == SamplingTelemetryType.TRACE) - .collect(Collectors.toList()); - List exceptionSamplingOverrides = - configuration.sampling.overrides.stream() - .filter( - override -> override.telemetryType == SamplingTelemetryType.EXCEPTION) - .collect(Collectors.toList()); + // the filtering log record processor needs to be chained on front of the batch log + // record processor, hopefully log filtering will be better supported by + // OpenTelemetry SDK in the future, see + // https://github.com/open-telemetry/opentelemetry-specification/pull/4439 logFilteringProcessor = - new AzureMonitorLogFilteringProcessor( - logSamplingOverrides, - exceptionSamplingOverrides, - (BatchLogRecordProcessor) logRecordProcessor, - configuration.instrumentation.logging.getSeverityThreshold()); + createLogFilteringProcessor(logRecordProcessor, configuration); logRecordProcessors.add(logFilteringProcessor); return LogRecordProcessor.composite( @@ -335,6 +322,25 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { }); } + private static AzureMonitorLogFilteringProcessor createLogFilteringProcessor( + LogRecordProcessor logRecordProcessor, Configuration configuration) { + + List logSamplingOverrides = + configuration.sampling.overrides.stream() + .filter(override -> override.telemetryType == SamplingTelemetryType.TRACE) + .collect(Collectors.toList()); + List exceptionSamplingOverrides = + configuration.sampling.overrides.stream() + .filter(override -> override.telemetryType == SamplingTelemetryType.EXCEPTION) + .collect(Collectors.toList()); + + return new AzureMonitorLogFilteringProcessor( + logSamplingOverrides, + exceptionSamplingOverrides, + logRecordProcessor, + configuration.instrumentation.logging.getSeverityThreshold()); + } + private static SpanExporter buildTraceExporter( Configuration configuration, TelemetryClient telemetryClient, QuickPulse quickPulse) { List exceptionSamplingOverrides = From 07f0e4beb5b08a41bf736ea85a338ab55e0c6660 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 25 Mar 2025 14:05:36 -0700 Subject: [PATCH 10/11] Update agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogProcessor.java Co-authored-by: Jean Bisutti --- .../agent/internal/init/AzureMonitorLogProcessor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogProcessor.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogProcessor.java index 6bb42702f24..8ea4ba58fdb 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogProcessor.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogProcessor.java @@ -11,6 +11,7 @@ import io.opentelemetry.sdk.logs.ReadWriteLogRecord; import io.opentelemetry.sdk.trace.ReadableSpan; +// See also AzureMonitorLogFilteringProcessor public class AzureMonitorLogProcessor implements LogRecordProcessor { @Override From b1a9dbea79f5121539f5b9fea3333a9e84ab25f8 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 25 Mar 2025 14:09:02 -0700 Subject: [PATCH 11/11] extract method --- .../agent/internal/init/SecondEntryPoint.java | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java index 3b9ff699524..e891a65594b 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java @@ -260,25 +260,6 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { return props; }) .addPropertiesCustomizer(new AiConfigCustomizer()) - .addLogRecordProcessorCustomizer( - (logRecordProcessor, configProperties) -> { - if (logRecordProcessor instanceof BatchLogRecordProcessor) { - List logRecordProcessors = - getLogRecordProcessors(configuration); - - // the filtering log record processor needs to be chained on front of the batch log - // record processor, hopefully log filtering will be better supported by - // OpenTelemetry SDK in the future, see - // https://github.com/open-telemetry/opentelemetry-specification/pull/4439 - logFilteringProcessor = - createLogFilteringProcessor(logRecordProcessor, configuration); - - logRecordProcessors.add(logFilteringProcessor); - return LogRecordProcessor.composite( - logRecordProcessors.toArray(new LogRecordProcessor[0])); - } - return logRecordProcessor; - }) .addSpanExporterCustomizer( (spanExporter, configProperties) -> { if (spanExporter instanceof AzureMonitorSpanExporterProvider.MarkerSpanExporter) { @@ -295,6 +276,13 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { return metricExporter; } }) + .addLogRecordProcessorCustomizer( + (logRecordProcessor, configProperties) -> { + if (logRecordProcessor instanceof BatchLogRecordProcessor) { + return wrapBatchLogRecordProcessor(logRecordProcessor, configuration); + } + return logRecordProcessor; + }) .addLogRecordExporterCustomizer( (logRecordExporter, configProperties) -> { if (logRecordExporter @@ -322,6 +310,20 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { }); } + private static LogRecordProcessor wrapBatchLogRecordProcessor( + LogRecordProcessor logRecordProcessor, Configuration configuration) { + List logRecordProcessors = getLogRecordProcessors(configuration); + + // the filtering log record processor needs to be chained on front of the batch log + // record processor, hopefully log filtering will be better supported by + // OpenTelemetry SDK in the future, see + // https://github.com/open-telemetry/opentelemetry-specification/pull/4439 + logFilteringProcessor = createLogFilteringProcessor(logRecordProcessor, configuration); + + logRecordProcessors.add(logFilteringProcessor); + return LogRecordProcessor.composite(logRecordProcessors.toArray(new LogRecordProcessor[0])); + } + private static AzureMonitorLogFilteringProcessor createLogFilteringProcessor( LogRecordProcessor logRecordProcessor, Configuration configuration) {