Macross.OpenTelemetry.Extensions is a .NET Standard 2.0+ library for extending what is provided by the official OpenTelemetry .NET library.
Most OpenTelemetry .NET instrumentation libraries provide an options object with
an Enrich callback for the purpose of adding additional data to the spans
(Activity) created by the instrumentation. The challenge with these callbacks
is they are global. Sometimes you want to add contextual information that isn't
available globally.
ActivityEnrichmentScope works a lot like ILogger scopes in that it wraps an
operation with information that can be accessed later (until the scope is
disposed). A span processor is provided which automatically calls the enrichment
callback when any span is ended under the scope.
Example:
public void PerformAction()
{
using (IDisposable scope = ActivityEnrichmentScope.Begin(EnrichActivity, myUser))
{
return CallService(myUser.Id);
}
static void EnrichActivity(Activity activity, MyUser user)
{
activity.SetTag("service.username", user.Username);
}
}To enable ActivityEnrichmentScope use the
AddActivityEnrichmentScopeProcessor extension in your startup code:
using IDisposable sdk = Sdk.CreateTracerProviderBuilder()
.AddActivityEnrichmentScopeProcessor()
.Build();Blog: https://blog.macrosssoftware.com/index.php/2021/01/27/troubleshooting-opentelemetry-net/
The individual OpenTelemetry components each write to their own EventSource instances for internal error logging and debugging. For example the SDK project uses OpenTelemetrySdkEventSource.cs.
There is an official mechanism for trapping these events outlined in the Troubleshooting section of the SDK README, however it involves writing to a flat file on the disk which must be retrieved and diagnosed.
For situations where file system access is unavailable, the
AddOpenTelemetryEventLogging extension method is provided to enable the
automatic writing of the internal OpenTelemetry events to the hosting
application's log pipeline.
Call the AddOpenTelemetryEventLogging extension in your ConfigureServices
method:
public class Startup
{
private readonly IConfiguration _Configuration;
public Startup(IConfiguration configuration)
{
_Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
if (_Configuration.GetValue<bool>("LogOpenTelemetryEvents"))
services.AddOpenTelemetryEventLogging();
}
}
In the example above the logging is only turned on when the application
configuration has LogOpenTelemetryEvents set to true.
By default the extension will listen to all OpenTelemetry sources
(^OpenTelemetry.*$) and will log any events at the EventLevel.Warning level
or lower (more severe), but this can be configured.
At runtime:
if (_Configuration.GetValue<bool>("LogOpenTelemetryEvents"))
{
services.AddOpenTelemetryEventLogging(options =>
{
options.EventSources = new[]
{
new OpenTelemetryEventLoggingSourceOptions
{
EventSourceRegExExpression = "^OpenTelemetry-Extensions-Hosting$",
EventLevel = EventLevel.Critical
},
new OpenTelemetryEventLoggingSourceOptions
{
EventSourceRegExExpression = "^OpenTelemetry-Exporter-Zipkin$",
EventLevel = EventLevel.Verbose
},
};
});
}Or by binding the options to a configuration section:
appsettings.json:
{
"LogOpenTelemetryEvents": true,
"OpenTelemetryListener": {
"EventSources": [
{
"EventSourceRegExExpression": "^OpenTelemetry-Exporter-Zipkin$",
"EventLevel": "Verbose"
}
]
}
}Startup.cs:
if (_Configuration.GetValue<bool>("LogOpenTelemetryEvents"))
{
services.Configure<OpenTelemetryEventLoggingOptions>(_Configuration.GetSection("OpenTelemetryListener"));
services.AddOpenTelemetryEventLogging();
}To change the format of the ILogger message that is written for triggered
events override the LogAction property:
public void ConfigureServices(IServiceCollection services)
{
if (_Configuration.GetValue<bool>("LogOpenTelemetryEvents"))
{
services.AddOpenTelemetryEventLogging(options => options.LogAction = OnLogMessageWritten);
}
}
private void OnLogMessageWritten(ILogger logger, LogLevel logLevel, EventWrittenEventArgs openTelemetryEvent)
{
logger.Log(
logLevel,
openTelemetryEvent.EventId,
"OpenTelemetryEvent - [{otelEventName}] {Message}",
openTelemetryEvent.EventName,
openTelemetryEvent.Message);
}Blog: https://blog.macrosssoftware.com/index.php/2021/01/30/using-opentelemetry-while-debugging/
The
ActivityListener
class is provided by the runtime for listening to all Activity objects created
in the process (optionally filtered by
ActivitySource
name).
There is no built-in mechanism for listening for only the Activity objects
created under a specific
TraceId.
To add this functionality System.Diagnostics.ActivityTraceListenerManager is provided.
-
Call the
AddActivityTraceListenerextension in yourConfigureServicesmethod:public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddActivityTraceListener(); } }
This will register the
ActivityTraceListenerManagerwith theIServiceProvider. -
Configure sampling:
-
If you want to have children created under a trace automatically sampled when a listener is registered, call the
SetActivityTraceListenerSamplerextension where you configure OpenTelemetry:services.AddOpenTelemetryTracing((serviceProvider, builder) => { builder.SetActivityTraceListenerSampler(new ParentBasedSampler(new AlwaysOnSampler())); };
The
innerSamplerparameter is the sampler which will be used when a trace listener is NOT registered. The default behavior is shown (ParentBasedSamplerw/AlwaysOnSampler). -
If you don't want to turn on automatic sampling, use the
AutomaticallySampleChildrenoption when you callAddActivityTraceListener(see step 1):public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddActivityTraceListener(o => o.AutomaticallySampleChildren = false); } }
You do NOT need to call
SetActivityTraceListenerSamplerin this case.
-
-
Use the
ActivityTraceListenerManagerto register a listener:using System.Diagnostics; public class TraceCaptureMiddleware { private readonly RequestDelegate _Next; private readonly ActivityTraceListenerManager _ActivityTraceListenerManager; public TraceCaptureMiddleware(RequestDelegate next, ActivityTraceListenerManager activityTraceListenerManager) { _Next = next ?? throw new ArgumentNullException(nameof(next)); _ActivityTraceListenerManager = activityTraceListenerManager ?? throw new ArgumentNullException(nameof(activityTraceListenerManager)); } public async Task InvokeAsync(HttpContext context) { using IActivityTraceListener? activityTraceListener = ShouldCaptureTrace(context) ? _ActivityTraceListenerManager.RegisterTraceListener(Activity.Current)) : null; try { await _Next(context).ConfigureAwait(false); } finally { if (activityTraceListener?.CompletedActivities.Count > 0) { // TODO: Do something interesting with the captured data. } } } private static bool ShouldCaptureTrace(HttpContext context) { // TODO: Trigger trace capture using some mechanism. } }
There are a few callback actions which are provided primarily for logging events
that occur inside the ActivityTraceListenerManager but the important setting
is:
ActivityTraceListenerManagerOptions.CleanupIntervalInMilliseconds
ActivityTraceListenerManager is expensive. It will cause all Activity
objects to be created and populated with data for the observed trace. It is best
used in a debugging or troubleshooting capacity. To that end, the
ActivityTraceListenerManager will clean itself up and stop listening once it
has been inactive for at least CleanupIntervalInMilliseconds. The default
value is 20 minutes.
To configure options a configure callback parameter is provided on the
AddActivityTraceListener method or you can bind the options to configuration:
services.Configure<ActivityTraceListenerManagerOptions>(_Configuration.GetSection("ActivityTracing"));