| File: Dcp\OtlpEndpointReferenceGatherer.cs | Web Access |
| Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting) |
// 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.Hosting.ApplicationModel; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Aspire.Hosting.Dcp; /// <summary> /// For containers, it replaces OTLP endpoint environemnt variable value with a reference to dashboard OTLP ingestion endpoint. /// </summary> /// <remarks> /// In run mode, the dashboard plays the role of an OTLP collector, but the dashboard resouce is added dynamically, /// just before the application started. That is why the OTLP configuration extension methods use configuration only. /// OTOH, DCP has full model to work with, and can replace the OTLP endpoint environment variables with references /// to the dashboard OTLP ingestion endpoint. For containers this allows DCP to tunnel these properly into container networks. /// </remarks> internal class OtlpEndpointReferenceGatherer : IExecutionConfigurationGatherer { public async ValueTask GatherAsync(IExecutionConfigurationGathererContext context, IResource resource, ILogger resourceLogger, DistributedApplicationExecutionContext executionContext, CancellationToken cancellationToken = default) { if (!resource.IsContainer() || !resource.TryGetLastAnnotation<OtlpExporterAnnotation>(out var oea)) { // This gatherer is only relevant for container resources that emit OTEL telemetry. return; } if (!context.EnvironmentVariables.TryGetValue(KnownOtelConfigNames.ExporterOtlpEndpoint, out _)) { // If the OTLP endpoint is not set, do not try to set it. return; } var model = executionContext.ServiceProvider.GetService<DistributedApplicationModel>(); if (model is null) { // Tests may not have a full model return; } var dashboardResource = model.Resources.SingleOrDefault(r => StringComparers.ResourceName.Equals(r.Name, KnownResourceNames.AspireDashboard)) as IResourceWithEndpoints; if (dashboardResource == null) { // Most test runs do not include the dashboard, and that's ok. If the dashboard is not present, do not try to set the OTLP endpoint. return; } if (!dashboardResource.TryGetEndpoints(out var dashboardEndpoints)) { Debug.Fail("Dashboard does not have any endpoints??"); return; } var grpcEndpoint = dashboardEndpoints.FirstOrDefault(e => e.Name == KnownEndpointNames.OtlpGrpcEndpointName); var httpEndpoint = dashboardEndpoints.FirstOrDefault(e => e.Name == KnownEndpointNames.OtlpHttpEndpointName); var resourceNetwork = resource.GetDefaultResourceNetwork(); var endpointReference = (oea.RequiredProtocol, grpcEndpoint, httpEndpoint) switch { (OtlpProtocol.Grpc, not null, _) => new EndpointReference(dashboardResource, grpcEndpoint, resourceNetwork), (OtlpProtocol.HttpProtobuf or OtlpProtocol.HttpJson, _, not null) => new EndpointReference(dashboardResource, httpEndpoint, resourceNetwork), (_, not null, _) => new EndpointReference(dashboardResource, grpcEndpoint, resourceNetwork), (_, _, not null) => new EndpointReference(dashboardResource, httpEndpoint, resourceNetwork), _ => null }; Debug.Assert(endpointReference != null, "Dashboard should have at least one matching OTLP endpoint"); if (endpointReference is not null) { ValueProviderContext vpc = new() { ExecutionContext = executionContext, Caller = resource, Network = resourceNetwork }; var url = await endpointReference.GetValueAsync(vpc, cancellationToken).ConfigureAwait(false); Debug.Assert(url is not null, $"We should be able to get a URL value from the reference dashboard endpoint '{endpointReference.EndpointName}'"); if (url is not null) { context.EnvironmentVariables[KnownOtelConfigNames.ExporterOtlpEndpoint] = url; } } } }