| File: tests\Shared\DashboardTelemetryFixture.cs | Web Access |
| Project: src\tests\Aspire.Dashboard.Components.Tests\Aspire.Dashboard.Components.Tests.csproj (Aspire.Dashboard.Components.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 System.Threading.Channels; using Aspire.Dashboard.Telemetry; using Aspire.Shared.Telemetry; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; using OpenTelemetry; using OpenTelemetry.Logs; using OpenTelemetry.Resources; using OpenTelemetry.Trace; namespace Aspire.Dashboard.Tests; public sealed class DashboardTelemetryFixture : IDisposable { private readonly ActivityListener _listener; private readonly List<AzureMonitorTelemetryProvider> _providers = []; public string ActivitySourceName { get; } = $"Test.Dashboard.{Guid.NewGuid():N}"; public string DiagnosticsActivitySourceName => ActivitySourceName + ".Diagnostics"; public Channel<Activity> ActivityChannel { get; } = Channel.CreateUnbounded<Activity>(); public Channel<TestDashboardTelemetryLog> LogChannel { get; } = Channel.CreateUnbounded<TestDashboardTelemetryLog>(); public ILoggerFactory LoggerFactory { get; } public ILogger EventLogger { get; } public TestSink LocalLogSink { get; } = new(); public DashboardTelemetryConfiguration Configuration { get; } public DashboardTelemetryService Telemetry { get; } public DashboardTelemetryFixture(bool reportedTelemetryEnabled = true, ActivitySamplingResult sampleResult = ActivitySamplingResult.AllDataAndRecorded, ILogger<DashboardTelemetryService>? logger = null) { Configuration = new() { ReportedTelemetryEnabled = reportedTelemetryEnabled }; LoggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder => { builder.AddProvider(new TestLoggerProvider(LocalLogSink)); }); EventLogger = LoggerFactory.CreateLogger(DashboardTelemetryService.EventLogCategoryName); _listener = new ActivityListener { ShouldListenTo = source => source.Name == ActivitySourceName, Sample = (ref ActivityCreationOptions<ActivityContext> _) => sampleResult, ActivityStopped = activity => ActivityChannel.Writer.TryWrite(activity) }; ActivitySource.AddActivityListener(_listener); Telemetry = new DashboardTelemetryService(logger ?? LoggerFactory.CreateLogger<DashboardTelemetryService>(), Configuration, ActivitySourceName, DiagnosticsActivitySourceName); ConfigureLogging(Telemetry); } public void ConfigureLogging(AspireTelemetryBase telemetry) { if (!Configuration.ReportedTelemetryEnabled) { return; } var logProvider = AzureMonitorTelemetryProvider.Create(new ServiceCollection(), ResourceBuilder.CreateEmpty(), DashboardTelemetryService.EventLogCategoryName, () => Sdk.CreateTracerProviderBuilder().Build(), provider => provider.AddProcessor(new EventLogProcessor(LogChannel.Writer))); try { telemetry.SetEventLogger(logProvider.EventLogger); _providers.Add(logProvider); } catch { logProvider.Dispose(); throw; } } public void Dispose() { Telemetry.Dispose(); foreach (var logProvider in _providers) { logProvider.Dispose(); } _listener.Dispose(); LoggerFactory.Dispose(); ActivityChannel.Writer.TryComplete(); LogChannel.Writer.TryComplete(); } private sealed class EventLogProcessor(ChannelWriter<TestDashboardTelemetryLog> writer) : BaseProcessor<LogRecord> { public override void OnEnd(LogRecord data) { writer.TryWrite(TestDashboardTelemetryLog.Create(data)); } } } public sealed record TestDashboardTelemetryLog( string? Message, LogLevel Level, EventId EventId, string? CategoryName, ActivityTraceId TraceId, ActivitySpanId SpanId, IReadOnlyList<KeyValuePair<string, object?>> Attributes) { internal static TestDashboardTelemetryLog Create(LogRecord data) { // LogRecord instances are pooled, so copy their data before returning to the SDK. return new(data.FormattedMessage, data.LogLevel, data.EventId, data.CategoryName, data.TraceId, data.SpanId, data.Attributes?.ToArray() ?? []); } } internal sealed class TestDashboardTelemetryLogExporter : BaseExporter<LogRecord> { public Channel<TestDashboardTelemetryLog> LogChannel { get; } = Channel.CreateUnbounded<TestDashboardTelemetryLog>(); public bool IsDisposed { get; private set; } public override ExportResult Export(in Batch<LogRecord> batch) { foreach (var record in batch) { LogChannel.Writer.TryWrite(TestDashboardTelemetryLog.Create(record)); } return ExportResult.Success; } protected override void Dispose(bool disposing) { IsDisposed = true; base.Dispose(disposing); } }