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
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

import com.azure.monitor.opentelemetry.exporter.implementation.AiOperationNameSpanProcessor;
import com.azure.monitor.opentelemetry.exporter.implementation.AiSemanticAttributes;
import com.azure.monitor.opentelemetry.exporter.implementation.OperationNames;
import com.azure.monitor.opentelemetry.exporter.implementation.SamplingScoreGeneratorV2;
import com.azure.monitor.opentelemetry.exporter.implementation.builders.AbstractTelemetryBuilder;
import com.azure.monitor.opentelemetry.exporter.implementation.builders.EventTelemetryBuilder;
import com.azure.monitor.opentelemetry.exporter.implementation.builders.ExceptionTelemetryBuilder;
Expand All @@ -39,16 +41,12 @@
import com.azure.monitor.opentelemetry.exporter.implementation.utils.FormattedDuration;
import com.azure.monitor.opentelemetry.exporter.implementation.utils.FormattedTime;
import com.azure.monitor.opentelemetry.exporter.implementation.utils.Strings;
import com.azure.monitor.opentelemetry.exporter.implementation.utils.TelemetryUtil;
import com.microsoft.applicationinsights.agent.bootstrap.BytecodeUtil.BytecodeUtilDelegate;
import com.microsoft.applicationinsights.agent.internal.legacyheaders.AiLegacyPropagator;
import com.microsoft.applicationinsights.agent.internal.sampling.SamplingScoreGeneratorV2;
import com.microsoft.applicationinsights.agent.internal.statsbeat.FeatureStatsbeat;
import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryClient;
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.trace.ReadableSpan;
import java.net.URI;
import java.net.URL;
Expand Down Expand Up @@ -417,78 +415,37 @@ public void logErrorOnce(Throwable t) {
private static void track(
AbstractTelemetryBuilder telemetryBuilder, Map<String, String> tags, boolean applySampling) {

String operationId = tags.get(ContextTagKeys.AI_OPERATION_ID.toString());
String existingOperationId = tags.get(ContextTagKeys.AI_OPERATION_ID.toString());

SpanContext context = Span.current().getSpanContext();
if (context.isValid()) {
String operationParentId = tags.get(ContextTagKeys.AI_OPERATION_PARENT_ID.toString());
String operationName = tags.get(ContextTagKeys.AI_OPERATION_NAME.toString());
Span span = Span.current();
SpanContext spanContext = span.getSpanContext();

trackInsideValidSpanContext(
telemetryBuilder, operationId, operationParentId, operationName, context, applySampling);
} else {
trackAsStandalone(telemetryBuilder, operationId, applySampling);
}
boolean isPartOfTheCurrentTrace =
spanContext.isValid()
&& (existingOperationId == null
|| existingOperationId.equals(spanContext.getTraceId()));

if (featureStatsbeat != null) {
featureStatsbeat.track2xBridgeUsage();
}
}

private static void trackInsideValidSpanContext(
AbstractTelemetryBuilder telemetryBuilder,
@Nullable String operationId,
@Nullable String operationParentId,
@Nullable String operationName,
SpanContext spanContext,
boolean applySampling) {

if (operationId != null && !operationId.equals(spanContext.getTraceId())) {
trackAsStandalone(telemetryBuilder, operationId, applySampling);
if (isPartOfTheCurrentTrace && applySampling && !spanContext.isSampled()) {
// no need to do anything more, sampled out
return;
}

if (!spanContext.isSampled()) {
// sampled out
return;
if (isPartOfTheCurrentTrace) {
setOperationTagsFromTheCurrentSpan(
telemetryBuilder, tags, existingOperationId, spanContext, span);
}

telemetryBuilder.addTag(ContextTagKeys.AI_OPERATION_ID.toString(), spanContext.getTraceId());

if (operationParentId == null) {
telemetryBuilder.addTag(
ContextTagKeys.AI_OPERATION_PARENT_ID.toString(), spanContext.getSpanId());
}

if (operationName == null) {
Span localRootSpan = LocalRootSpan.fromContextOrNull(Context.current());
if (localRootSpan instanceof ReadableSpan) {
telemetryBuilder.addTag(
ContextTagKeys.AI_OPERATION_NAME.toString(),
AiOperationNameSpanProcessor.getOperationName((ReadableSpan) localRootSpan));
}
}

if (applySampling) {
float samplingPercentage =
TelemetryUtil.getSamplingPercentage(
spanContext.getTraceState(), BytecodeUtilImpl.samplingPercentage, false);

if (samplingPercentage != 100) {
telemetryBuilder.setSampleRate(samplingPercentage);
if (isPartOfTheCurrentTrace && applySampling && span instanceof ReadableSpan) {
Long itemCount = ((ReadableSpan) span).getAttribute(AiSemanticAttributes.ITEM_COUNT);
if (itemCount != null && itemCount != 1) {
telemetryBuilder.setSampleRate(100.0f / itemCount);
}
}
// this is not null because sdk instrumentation is not added until TelemetryClient.setActive()
// is called
TelemetryClient.getActive().trackAsync(telemetryBuilder.build());
}

private static void trackAsStandalone(
AbstractTelemetryBuilder telemetryBuilder, String operationId, boolean applySampling) {
if (applySampling) {
// sampling is done using the configured sampling percentage
if (!isPartOfTheCurrentTrace && applySampling) {
// standalone sampling is done using the configured sampling percentage
float samplingPercentage = BytecodeUtilImpl.samplingPercentage;
if (!sample(operationId, samplingPercentage)) {
if (!sample(existingOperationId, samplingPercentage)) {
logger.debug("Item {} sampled out", telemetryBuilder.getClass().getSimpleName());
// sampled out
return;
Expand All @@ -503,6 +460,33 @@ private static void trackAsStandalone(
// this is not null because sdk instrumentation is not added until TelemetryClient.setActive()
// is called
TelemetryClient.getActive().trackAsync(telemetryBuilder.build());

if (featureStatsbeat != null) {
featureStatsbeat.track2xBridgeUsage();
}
}

private static void setOperationTagsFromTheCurrentSpan(
AbstractTelemetryBuilder telemetryBuilder,
Map<String, String> tags,
String existingOperationId,
SpanContext spanContext,
Span span) {

if (existingOperationId == null) {
telemetryBuilder.addTag(ContextTagKeys.AI_OPERATION_ID.toString(), spanContext.getTraceId());
}
String existingOperationParentId = tags.get(ContextTagKeys.AI_OPERATION_PARENT_ID.toString());
if (existingOperationParentId == null) {
telemetryBuilder.addTag(
ContextTagKeys.AI_OPERATION_PARENT_ID.toString(), spanContext.getSpanId());
}
String existingOperationName = tags.get(ContextTagKeys.AI_OPERATION_NAME.toString());
if (existingOperationName == null && span instanceof ReadableSpan) {
telemetryBuilder.addTag(
ContextTagKeys.AI_OPERATION_NAME.toString(),
OperationNames.getOperationName((ReadableSpan) span));
}
}

private static boolean sample(String operationId, double samplingPercentage) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,25 @@
* DEALINGS IN THE SOFTWARE.
*/

package com.azure.monitor.opentelemetry.exporter.implementation.utils;
package com.microsoft.applicationinsights.agent.internal.classicsdk;

import com.azure.monitor.opentelemetry.exporter.implementation.builders.ExceptionDetailBuilder;
import com.azure.monitor.opentelemetry.exporter.implementation.builders.StackFrameBuilder;
import io.opentelemetry.api.trace.TraceState;
import io.opentelemetry.instrumentation.api.internal.cache.Cache;
import com.azure.monitor.opentelemetry.exporter.implementation.utils.Strings;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// naming convention:
// * MonitorDomain data
// * TelemetryItem telemetry
public class TelemetryUtil {
class TelemetryUtil {

private static final int MAX_PARSED_STACK_LENGTH =
32768; // Breeze will reject parsedStack exceeding 65536 bytes. Each char is 2 bytes long.
private static final Logger logger = LoggerFactory.getLogger(TelemetryUtil.class);

// Breeze will reject parsedStack exceeding 65536 bytes. Each char is 2 bytes long.
private static final int MAX_PARSED_STACK_LENGTH = 32768;

public static List<ExceptionDetailBuilder> getExceptions(Throwable throwable) {
static List<ExceptionDetailBuilder> getExceptions(Throwable throwable) {
List<ExceptionDetailBuilder> exceptions = new ArrayList<>();
convertExceptionTree(throwable, null, exceptions, Integer.MAX_VALUE);
return exceptions;
Expand Down Expand Up @@ -150,74 +147,5 @@ private static int getStackFrameLength(@Nullable String text) {
return text == null ? 0 : text.length();
}

public static final String SAMPLING_PERCENTAGE_TRACE_STATE = "ai-internal-sp";

private static final Cache<String, OptionalFloat> parsedSamplingPercentageCache =
Cache.bounded(100);

private static final AtomicBoolean alreadyLoggedSamplingPercentageMissing = new AtomicBoolean();
private static final AtomicBoolean alreadyLoggedSamplingPercentageParseError =
new AtomicBoolean();

private static final Logger logger = LoggerFactory.getLogger(TelemetryUtil.class);

public static float getSamplingPercentage(
TraceState traceState, float defaultValue, boolean warnOnMissing) {
String samplingPercentageStr = traceState.get(SAMPLING_PERCENTAGE_TRACE_STATE);
if (samplingPercentageStr == null) {
if (warnOnMissing && !alreadyLoggedSamplingPercentageMissing.getAndSet(true)) {
// sampler should have set the trace state
logger.warn("did not find sampling percentage in trace state: {}", traceState);
}
return defaultValue;
}
return parseSamplingPercentage(samplingPercentageStr).orElse(defaultValue);
}

private static OptionalFloat parseSamplingPercentage(String samplingPercentageStr) {
return parsedSamplingPercentageCache.computeIfAbsent(
samplingPercentageStr,
str -> {
try {
return OptionalFloat.of(Float.parseFloat(str));
} catch (NumberFormatException e) {
if (!alreadyLoggedSamplingPercentageParseError.getAndSet(true)) {
logger.warn("error parsing sampling percentage trace state: {}", str, e);
}
return OptionalFloat.empty();
}
});
}

private static class OptionalFloat {

private static final OptionalFloat EMPTY = new OptionalFloat();

private final boolean present;
private final float value;

private OptionalFloat() {
this.present = false;
this.value = Float.NaN;
}

private OptionalFloat(float value) {
this.present = true;
this.value = value;
}

public static OptionalFloat empty() {
return EMPTY;
}

public static OptionalFloat of(float value) {
return new OptionalFloat(value);
}

public float orElse(float other) {
return present ? value : other;
}
}

private TelemetryUtil() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,28 @@ public static class Sampling {

public static class SamplingPreview {

// this is not the default for now at least, because
//
// parent not-sampled -> child not-sampled (always, to avoid broken traces)
// parent sampled -> child will still sample at its desired rate
// note: this is just sample rate of child, not sample rate of parent times
// sample rate of child, as both are using the same trace-id hash
//
// ??? if child sample rate is higher than the parent sample rate
// parent sampled --> child sampled
// parent not-sampled --> child not-sampled
// which means that child has same effective sample rate as parent, and so its item count
// will be wrong
//
// AND SO: if want to use parent-based sampler, then need to propagate the sample rate,
// otherwise can end up with incorrect math
//
// Another (lesser) reason is because .NET SDK always propagates trace flags "00" (not
// sampled)
//
// IMPORTANT if changing this default, we need to keep it at least on Azure Functions
public boolean parentBased;

public List<SamplingOverride> overrides = new ArrayList<>();
}

Expand Down Expand Up @@ -261,10 +283,8 @@ public static class PreviewConfiguration {
// world,
// so safer to only allow single interval for now
public int metricIntervalSeconds = 60;
// ignoreRemoteParentNotSampled is sometimes needed because .NET SDK always propagates trace
// flags "00" (not sampled)
// in particular, it is always needed in Azure Functions worker
public boolean ignoreRemoteParentNotSampled = DiagnosticsHelper.rpIntegrationChar() == 'f';
// this is just here to detect if using this old setting in order to give a helpful message
@Deprecated public Boolean ignoreRemoteParentNotSampled;
public boolean captureControllerSpans;
// this is just here to detect if using this old setting in order to give a helpful message
@Deprecated public boolean httpMethodInOperationName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ public static Configuration create(Path agentJarPath, @Nullable RpConfiguration
"\"httpMethodInOperationName\" is no longer in preview and it is now the"
+ " (one and only) default behavior");
}
if (config.preview.ignoreRemoteParentNotSampled != null) {
configurationLogger.warn(
"\"ignoreRemoteParentNotSampled\" has been deprecated and"
+ " \"ignoreRemoteParentNotSampled\": false has replaced with"
+ " \"preview\": { \"sampling\": { \"parentBased\": true } } (while"
+ " \"ignoreRemoteParentNotSampled\": true is just the default behavior)");
}
if (config.preview.openTelemetryApiSupport) {
configurationLogger.warn(
"\"openTelemetryApiSupport\" is no longer in preview and it is now the"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import com.azure.core.http.HttpResponse;
import com.azure.monitor.opentelemetry.exporter.implementation.configuration.ConnectionString;
import com.azure.monitor.opentelemetry.exporter.implementation.logging.NetworkFriendlyExceptions;
import com.azure.monitor.opentelemetry.exporter.implementation.logging.WarningLogger;
import com.azure.monitor.opentelemetry.exporter.implementation.logging.OperationLogger;
import com.azure.monitor.opentelemetry.exporter.implementation.utils.ThreadPoolUtils;
import com.microsoft.applicationinsights.agent.bootstrap.AiAppId;
import com.microsoft.applicationinsights.agent.internal.httpclient.LazyHttpClient;
Expand All @@ -53,8 +53,8 @@ public class AppIdSupplier implements AiAppId.Supplier {
Executors.newSingleThreadScheduledExecutor(
ThreadPoolUtils.createDaemonThreadFactory(AppIdSupplier.class));

private static final WarningLogger warningLogger =
new WarningLogger(GetAppIdTask.class, "Unable to retrieve appId");
private static final OperationLogger operationLogger =
new OperationLogger(GetAppIdTask.class, "Retrieving appId");

// guarded by taskLock
private GetAppIdTask task;
Expand Down Expand Up @@ -147,7 +147,7 @@ public void run() {
} catch (RuntimeException ex) {
if (!NetworkFriendlyExceptions.logSpecialOneTimeFriendlyException(
ex, url.toString(), friendlyExceptionThrown, logger)) {
warningLogger.recordWarning("exception sending request to " + url, ex, APP_ID_ERROR);
operationLogger.recordFailure("exception sending request to " + url, ex, APP_ID_ERROR);
}
backOff();
return;
Expand All @@ -161,7 +161,7 @@ public void run() {
String body = response.getBodyAsString().block();
int statusCode = response.getStatusCode();
if (statusCode != 200) {
warningLogger.recordWarning(
operationLogger.recordFailure(
"received " + statusCode + " from " + url + "\nfull response:\n" + body,
null,
APP_ID_ERROR);
Expand All @@ -171,12 +171,12 @@ public void run() {

// check for case when breeze returns invalid value
if (body == null || body.isEmpty()) {
warningLogger.recordWarning("received empty body from " + url, null, APP_ID_ERROR);
operationLogger.recordFailure("received empty body from " + url, null, APP_ID_ERROR);
backOff();
return;
}

logger.debug("appId retrieved: {}", body);
operationLogger.recordSuccess();
appId = body;
}

Expand Down
Loading