File: Telemetry\DashboardTelemetryLogExportTests.cs
Web Access
Project: src\tests\Aspire.Dashboard.Tests\Aspire.Dashboard.Tests.csproj (Aspire.Dashboard.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using Aspire.Dashboard.Telemetry;
using Aspire.Shared.Telemetry;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using OpenTelemetry;
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Xunit;
 
namespace Aspire.Dashboard.Tests.Telemetry;
 
public class DashboardTelemetryLogExportTests
{
    [Theory]
    [InlineData("OpenTelemetry", false)]
    [InlineData("OpenTelemetry", true)]
    [InlineData("OpenTelemetry.Logs.OpenTelemetryLoggerProvider", false)]
    [InlineData("OpenTelemetry.Logs.OpenTelemetryLoggerProvider", true)]
    public async Task ApplicationLoggingConfiguration_CannotControlProductExport(string providerName, bool suppressProductCategory)
    {
        var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?>
        {
            ["Logging:LogLevel:Default"] = "Trace",
            [$"Logging:LogLevel:{DashboardTelemetryService.EventLogCategoryName}"] = suppressProductCategory ? "None" : "Trace",
            [$"Logging:{providerName}:LogLevel:Microsoft.AspNetCore"] = "Trace",
            [$"Logging:{providerName}:LogLevel:{DashboardTelemetryService.EventLogCategoryName}"] = "Trace"
        }).Build();
        var sink = new TestSink();
        var services = new ServiceCollection();
        services.AddLogging(builder =>
        {
            builder.AddConfiguration(configuration.GetSection("Logging"));
            builder.AddProvider(new TestLoggerProvider(sink));
        });
        services.AddSingleton(new DashboardTelemetryConfiguration { ReportedTelemetryEnabled = true });
        services.AddSingleton<DashboardTelemetryService>();
        var exporter = new TestDashboardTelemetryLogExporter();
 
        using (var serviceProvider = services.BuildServiceProvider())
        {
            Assert.Null(serviceProvider.GetService<LoggerProvider>());
            var telemetry = serviceProvider.GetRequiredService<DashboardTelemetryService>();
            var processor = new BatchLogRecordExportProcessor(exporter);
            using var logProvider = AzureMonitorTelemetryProvider.Create(new ServiceCollection(), ResourceBuilder.CreateEmpty(),
                DashboardTelemetryService.EventLogCategoryName, () => Sdk.CreateTracerProviderBuilder().Build(), provider =>
            {
                provider.AddProcessor(processor);
                Assert.Same(provider, processor.ParentProvider);
                Assert.Same(provider, exporter.ParentProvider);
            });
            telemetry.SetEventLogger(logProvider.EventLogger);
            var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
            var frameworkLogger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Test");
            Assert.True(frameworkLogger.IsEnabled(LogLevel.Information));
            frameworkLogger.LogInformation("Ordinary framework log");
            frameworkLogger.LogError(new InvalidOperationException("secret exception"), "Raw framework error");
 
            var otherEventLogger = loggerFactory.CreateLogger(DashboardTelemetryService.EventLogCategoryName + ".Other");
            Assert.Equal(!suppressProductCategory, otherEventLogger.IsEnabled(LogLevel.Information));
            otherEventLogger.LogInformation("Wrong event category");
            var eventLogger = loggerFactory.CreateLogger(DashboardTelemetryService.EventLogCategoryName);
            Assert.Equal(!suppressProductCategory, eventLogger.IsEnabled(LogLevel.Debug));
            eventLogger.LogDebug("Verbose product log");
            eventLogger.LogInformation("Ordinary product-category log");
 
            using var activity = new Activity("ambient").Start();
            using (eventLogger.BeginScope(new Dictionary<string, object?> { ["secret"] = "workspace path" }))
            {
                telemetry.RecordEvent(
                    TelemetryEventKeys.ComponentInitialize);
            }
            Assert.True(await logProvider.ForceFlushAsync(timeoutMilliseconds: 5000));
 
            Assert.True(exporter.LogChannel.Reader.TryRead(out var log));
            Assert.Equal(TelemetryEventKeys.ComponentInitialize, log.Message);
            Assert.Equal(DashboardTelemetryService.EventLogCategoryName, log.CategoryName);
            Assert.Equal(LogLevel.Information, log.Level);
            Assert.Equal(activity.TraceId, log.TraceId);
            Assert.Equal(activity.SpanId, log.SpanId);
            Assert.Collection(log.Attributes.OrderBy(t => t.Key, StringComparer.Ordinal),
                tag => Assert.Equal(TelemetryPropertyKeys.DashboardBuildId, tag.Key),
                tag => Assert.Equal(TelemetryPropertyKeys.DashboardVersion, tag.Key),
                tag => Assert.Equal(new KeyValuePair<string, object?>("microsoft.operation_name", TelemetryEventKeys.ComponentInitialize), tag),
                tag => Assert.Equal(new KeyValuePair<string, object?>("{OriginalFormat}", TelemetryEventKeys.ComponentInitialize), tag));
            Assert.False(exporter.LogChannel.Reader.TryPeek(out _));
            Assert.Equal(suppressProductCategory
                ? ["Ordinary framework log", "Raw framework error"]
                : ["Ordinary framework log", "Raw framework error", "Wrong event category", "Verbose product log", "Ordinary product-category log"],
                sink.Writes.Select(write => write.Message).ToArray());
 
            telemetry.RecordEvent(
                TelemetryEventKeys.ComponentDispose);
            Assert.True(await logProvider.ShutdownAsync(timeoutMilliseconds: 5000));
            Assert.True(exporter.LogChannel.Reader.TryRead(out var shutdownLog));
            Assert.Equal(TelemetryEventKeys.ComponentDispose, shutdownLog.Message);
            Assert.Equal(TelemetryEventKeys.ComponentDispose,
                shutdownLog.Attributes.Single(t => t.Key == "microsoft.operation_name").Value);
            Assert.False(exporter.LogChannel.Reader.TryPeek(out _));
            Assert.False(exporter.IsDisposed);
        }
 
        Assert.True(exporter.IsDisposed);
    }
 
}