Search code examples
c#azureopen-telemetry.net-4.6.1

Azure Open Telemetry - Activity is null few min after app start


I have a windows service that is processing Azure service bus messages. When I deploy this code to QA server, the first few messages received, telemetry works great. I see all the information including the tags I expect to see. Then if there is no messages published for say 15 min, any message received after 15 min there is no data in telemetry. Is there some idle timeout or something else I am missing? I can confirm that the handleMessageActivity object is null and the activitySource still looks to be a valid object. Same problem happens on both activitySource?.CreateActivity and activitySource?.StartActivity I cannot understand why activitySource is valid object when the object is first deployed and then becomes null. If it helps I have also pasted the windsor DI setup part at the end. This is a framework 4.6.1 app

My code to setup telemetry

public class AzureOpenTelemetryLoggerService : IDisposable
{
    private Dictionary<ActivitySourceEnum, ActivitySource> ActivitySourceCollection;
    private bool disposedValue;
    private readonly TracerProvider _tracerProvider;
    public AzureOpenTelemetryLoggerService(string connectionString)
    {
        var sourcesArray = System.Enum.GetNames(typeof(ActivitySourceEnum));
        AppContext.SetSwitch("Azure.Experimental.EnableActivitySource", true);
        _tracerProvider = Sdk
                .CreateTracerProviderBuilder()
                .AddSource(sourcesArray)
                .SetSampler(new AlwaysOnSampler())
                .SetResourceBuilder(
                        ResourceBuilder.CreateDefault().AddService(
                            serviceName: "MyApp.Backend",
                            serviceVersion: "1.0.0")
                        )
                .AddConsoleExporter(configure =>
                {

                })
                .AddAzureMonitorTraceExporter(configure =>
                {

                    configure.ConnectionString = connectionString;
                })
                .Build();

        InitializeActivitySources();
    }

    private void InitializeActivitySources()
    {
        ActivitySourceCollection =
            new Dictionary<ActivitySourceEnum, ActivitySource>();
        foreach (var activitySourceItem
            in Enum
            .GetValues(typeof(ActivitySourceEnum))
            .Cast<ActivitySourceEnum>())
        {
            ActivitySourceCollection.Add(
                activitySourceItem,
                new ActivitySource(activitySourceItem.ToString())
            );
        };
        var activityListener = new ActivityListener
        {
            ShouldListenTo = s => true,
            SampleUsingParentId =
            (ref ActivityCreationOptions<string> activityOptions)
            => ActivitySamplingResult.AllData,
            Sample =
            (ref ActivityCreationOptions<ActivityContext> activityOptions)
            => ActivitySamplingResult.AllData
        };
        ActivitySource.AddActivityListener(activityListener);
    }

    public ActivitySource GetActivitySource(ActivitySourceEnum activitySource)
    {
        return ActivitySourceCollection[activitySource];
    }


    public enum ActivitySourceEnum
    {
        SomeActivitySourceToListenTo
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                _tracerProvider.Dispose();
            }
            disposedValue = true;
        }
    }

    public void Dispose()
    {
        // Do not change this code.
        // Put cleanup code in 'Dispose(bool disposing)' method
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
}

Client code to use this in some service class -- pseudoCode

Note I am injecting AzureOpenTelemetryLoggerService via Windsor DI which works as expected

public Task Handle(SomeChangeMessage message, IMessageHandlerContext context)
{
    using (var activitySource 
        = _azureOpenTelemetryLoggerService
        .GetActivitySource(AzureOpenTelemetryLoggerService
        .ActivitySourceEnum
        .SomeActivitySourceToListenTo))
    {
        // this is always a valid object
        Log.Debug("activitySource is created {@activitySource}", activitySource);
        using (var handleMessageActivity 
            = activitySource?
            .CreateActivity($"Handle SomeChangeMessage  message",
            ActivityKind.Internal))
        {
            // first time is valid object.
            // after 10-15 min when I process another message,
            // his object and below is null
            // so no temetry gets written
            Log.Debug("handleMessageActivity " +
                "is created {@handleMessageActivity}", 
                handleMessageActivity);
            handleMessageActivity?.SetTag("Tag1", "someVal");
            ActivityContext? linkedContext = handleMessageActivity?.Context;
            var links = new List<ActivityLink>();
            if (linkedContext.HasValue)
            {
                links.Add(new ActivityLink(linkedContext.Value));
            }
            foreach (var personId in message.SomeList.Select(a => a.personId)
                .Distinct())
            {
                using (var processPartyActivity = activitySource?
                    .StartActivity(kind: ActivityKind.Client, 
                    links: links, name: "process party"))
                {
                    Log.Information("Process start for {personId}", personId);
                    // more stuff
                    Log.Information("Process complete for  {personId}", personId);
                }
            }
        }
    }
    return Task.CompletedTask;
}

Windsor castle registration code:

container.Register(
                Component.For<AzureOpenTelemetryLoggerService>()
                    .ImplementedBy<AzureOpenTelemetryLoggerService>()
                    .DependsOn(
                        Dependency.OnAppSettingsValue("connectionString",
                        "AzureAppInsightsConnectionString"))
                    .LifestyleSingleton())

Result of first call: enter image description here

Result of second call: enter image description here


Solution

  • AzureOpenTelemetryLoggerService class was correct in it's implementation. In the method public Task Handle ....do not add "use " when getting activity from activitySource collection. I put use there..which called dispose when finishing off the use block. Obvious but I clearly missed it. Also per documentation, if you use CreateActivity, then you have to manually start and stop it. Instead use StartActivity. To link parent activity and child activity, do something like this

    if (handleMessageActivity != null)
                        {
                            processPartyActivity.SetParentId(handleMessageActivity?.Id);
                        }
    

    just putting the link didnt help as much as I wished. Hope this helps others