-
Notifications
You must be signed in to change notification settings - Fork 158
Add OpenTelemetry Collector extension/component #603
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
aaronpowell
merged 22 commits into
CommunityToolkit:main
from
martinjt:otel-collector-component
Sep 5, 2025
Merged
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
ef60fdf
Added OpenTelemetry Collector component
martinjt 5401d2d
Fixed DevCertHostingExtensions
martinjt 68d4102
Update src/Shared/DevCertHostingExtensions.cs
martinjt 81ffb8d
Apply suggestions from code review
martinjt 790c37a
Renames
martinjt 84a0ecf
Switch the image syntax
martinjt bd8de8a
Move to OnBeforeStart for DevCerts
martinjt 0240f5c
Updated Devcerts code
martinjt 8b54668
Updates to devcerts to check for duplicate mounts.
martinjt 19be5ba
Added an example of the collector usage
martinjt 5f9f442
Moved to eventing and made all other resources wait for this collector
martinjt f908dda
made the extension methods internal
martinjt 26c32d4
Added a basic test
martinjt 11d3af2
Reworked Extensions and added README with examples
martinjt 267b291
Added tests to workflow
martinjt 2b48b7a
Added more tests
martinjt abd19d4
object has a null guard earlier so we don't have to force it
aaronpowell 446c6a8
Fixing a CA1853 - Dictionary.Remove does its own guard check
aaronpowell 53e2270
Tightening up resource access
aaronpowell 760a610
Expanding the unit tests
aaronpowell 0c69e58
More tests
aaronpowell 52f1159
Adding martinjt as codeowner
aaronpowell File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next
Next commit
Added OpenTelemetry Collector component
- Loading branch information
commit ef60fdf0a466852aa369c4da0021828df6f7941b
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
src/CommunityToolkit.Aspire.Hosting.OpenTelemetryCollector/CollectorExtensions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| using Aspire.Hosting.ApplicationModel; | ||
| using Aspire.Hosting.Lifecycle; | ||
| using Microsoft.Extensions.Configuration; | ||
| using Microsoft.Extensions.Hosting; | ||
|
|
||
| namespace Aspire.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Extension methods to add the collector resource | ||
| /// </summary> | ||
| public static class CollectorExtensions | ||
| { | ||
| private const string DashboardOtlpUrlVariableNameLegacy = "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL"; | ||
| private const string DashboardOtlpUrlVariableName = "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL"; | ||
| private const string DashboardOtlpApiKeyVariableName = "AppHost:OtlpApiKey"; | ||
| private const string DashboardOtlpUrlDefaultValue = "http://localhost:18889"; | ||
|
|
||
| /// <summary> | ||
| /// Adds an OpenTelemetry Collector into the Aspire AppHost | ||
| /// </summary> | ||
| /// <param name="builder"></param> | ||
| /// <param name="name"></param> | ||
| /// <param name="configureSettings"></param> | ||
| /// <returns></returns> | ||
| public static IResourceBuilder<CollectorResource> AddOpenTelemetryCollector(this IDistributedApplicationBuilder builder, | ||
| string name, | ||
| Action<OpenTelemetryCollectorSettings> configureSettings = null!) | ||
martinjt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| var url = builder.Configuration[DashboardOtlpUrlVariableName] ?? | ||
| builder.Configuration[DashboardOtlpUrlVariableNameLegacy] ?? | ||
| DashboardOtlpUrlDefaultValue; | ||
|
|
||
| var settings = new OpenTelemetryCollectorSettings(); | ||
| configureSettings?.Invoke(settings); | ||
|
|
||
| var isHttpsEnabled = !settings.ForceNonSecureReceiver && url.StartsWith("https", StringComparison.OrdinalIgnoreCase); | ||
|
|
||
| var dashboardOtlpEndpoint = ReplaceLocalhostWithContainerHost(url, builder.Configuration); | ||
|
|
||
| var resource = new CollectorResource(name); | ||
| var resourceBuilder = builder.AddResource(resource) | ||
| .WithImage(settings.CollectorImage, settings.CollectorVersion) | ||
| .WithEnvironment("ASPIRE_ENDPOINT", dashboardOtlpEndpoint) | ||
| .WithEnvironment("ASPIRE_API_KEY", builder.Configuration[DashboardOtlpApiKeyVariableName]); | ||
|
|
||
| if (settings.EnableGrpcEndpoint) | ||
| resourceBuilder.WithEndpoint(targetPort: 4317, name: CollectorResource.GRPCEndpointName, scheme: isHttpsEnabled ? "https" : "http"); | ||
| if (settings.EnableHttpEndpoint) | ||
| resourceBuilder.WithEndpoint(targetPort: 4318, name: CollectorResource.HTTPEndpointName, scheme: isHttpsEnabled ? "https" : "http"); | ||
|
|
||
|
|
||
| if (!settings.ForceNonSecureReceiver && isHttpsEnabled && builder.ExecutionContext.IsRunMode && builder.Environment.IsDevelopment()) | ||
| { | ||
| DevCertHostingExtensions.RunWithHttpsDevCertificate(resourceBuilder, "HTTPS_CERT_FILE", "HTTPS_CERT_KEY_FILE", (certFilePath, certKeyPath) => | ||
| { | ||
| if (settings.EnableHttpEndpoint) | ||
| { | ||
| resourceBuilder.WithArgs( | ||
| $@"--config=yaml:receivers::otlp::protocols::http::tls::cert_file: ""{certFilePath}""", | ||
| $@"--config=yaml:receivers::otlp::protocols::http::tls::key_file: ""{certKeyPath}"""); | ||
| } | ||
| if (settings.EnableGrpcEndpoint) | ||
| { | ||
| resourceBuilder.WithArgs( | ||
| $@"--config=yaml:receivers::otlp::protocols::grpc::tls::cert_file: ""{certFilePath}""", | ||
| $@"--config=yaml:receivers::otlp::protocols::grpc::tls::key_file: ""{certKeyPath}"""); | ||
| } | ||
| }); | ||
| } | ||
| return resourceBuilder; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Force all apps to forward to the collector instead of the dashboard directly | ||
| /// </summary> | ||
| /// <param name="builder"></param> | ||
| /// <returns></returns> | ||
| public static IResourceBuilder<CollectorResource> WithAppForwarding(this IResourceBuilder<CollectorResource> builder) | ||
| { | ||
| builder.ApplicationBuilder.Services.TryAddLifecycleHook<EnvironmentVariableHook>(); | ||
martinjt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return builder; | ||
| } | ||
|
|
||
| private static string ReplaceLocalhostWithContainerHost(string value, IConfiguration configuration) | ||
| { | ||
| var hostName = configuration["AppHost:ContainerHostname"] ?? "host.docker.internal"; | ||
|
|
||
| return value.Replace("localhost", hostName, StringComparison.OrdinalIgnoreCase) | ||
| .Replace("127.0.0.1", hostName) | ||
| .Replace("[::1]", hostName); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds a config file to the collector | ||
| /// </summary> | ||
| /// <param name="builder"></param> | ||
| /// <param name="configPath"></param> | ||
| /// <returns></returns> | ||
| public static IResourceBuilder<CollectorResource> WithConfig(this IResourceBuilder<CollectorResource> builder, string configPath) | ||
| { | ||
| var configFileInfo = new FileInfo(configPath); | ||
| return builder.WithBindMount(configPath, $"/config/{configFileInfo.Name}") | ||
| .WithArgs($"--config=/config/{configFileInfo.Name}"); | ||
| } | ||
| } | ||
23 changes: 23 additions & 0 deletions
23
src/CommunityToolkit.Aspire.Hosting.OpenTelemetryCollector/CollectorResource.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| using Aspire.Hosting.ApplicationModel; | ||
|
|
||
| namespace Aspire.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// The collector resource | ||
| /// </summary> | ||
| /// <param name="name">Name of the resource</param> | ||
| public class CollectorResource(string name) : ContainerResource(name) | ||
martinjt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| internal static string GRPCEndpointName = "grpc"; | ||
| internal static string HTTPEndpointName = "http"; | ||
|
|
||
| /// <summary> | ||
| /// gRPC Endpoint | ||
| /// </summary> | ||
| public EndpointReference GRPCEndpoint => new(this, GRPCEndpointName); | ||
martinjt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// <summary> | ||
| /// HTTP Endpoint | ||
| /// </summary> | ||
| public EndpointReference HTTPEndpoint => new(this, HTTPEndpointName); | ||
martinjt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
11 changes: 11 additions & 0 deletions
11
...ting.OpenTelemetryCollector/CommunityToolkit.Aspire.Hosting.OpenTelemetryCollector.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <Description>An Aspire component to add an OpenTelemetry Collector into the OTLP pipeline</Description> | ||
| <AdditionalPackageTags>opentelemetry observability</AdditionalPackageTags> | ||
| </PropertyGroup> | ||
|
|
||
martinjt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <ItemGroup> | ||
| <PackageReference Include="Aspire.Hosting" /> | ||
| </ItemGroup> | ||
| </Project> | ||
58 changes: 58 additions & 0 deletions
58
src/CommunityToolkit.Aspire.Hosting.OpenTelemetryCollector/EnvironmentVariableHook.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| using Aspire.Hosting.ApplicationModel; | ||
| using Aspire.Hosting.Lifecycle; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace Aspire.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Hooks to add the OTLP environment variables to the various containers | ||
| /// </summary> | ||
| /// <param name="logger"></param> | ||
| public class EnvironmentVariableHook(ILogger<EnvironmentVariableHook> logger) : IDistributedApplicationLifecycleHook | ||
martinjt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| /// <inheritdoc /> | ||
| public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken) | ||
| { | ||
| var resources = appModel.GetProjectResources(); | ||
| var collectorResource = appModel.Resources.OfType<CollectorResource>().FirstOrDefault(); | ||
|
|
||
| if (collectorResource is null) | ||
| { | ||
| logger.LogWarning("No collector resource found"); | ||
| return Task.CompletedTask; | ||
| } | ||
|
|
||
| var grpcEndpoint = collectorResource!.GetEndpoint(collectorResource!.GRPCEndpoint.EndpointName); | ||
martinjt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| var httpEndpoint = collectorResource!.GetEndpoint(collectorResource!.HTTPEndpoint.EndpointName); | ||
martinjt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
martinjt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if (!resources.Any()) | ||
| { | ||
| logger.LogInformation("No resources to add Environment Variables to"); | ||
| } | ||
|
|
||
| foreach (var resourceItem in resources) | ||
| { | ||
| logger.LogDebug("Forwarding Telemetry for {name} to the collector", resourceItem.Name); | ||
| if (resourceItem is null) continue; | ||
|
|
||
| resourceItem.Annotations.Add(new EnvironmentCallbackAnnotation((EnvironmentCallbackContext context) => | ||
| { | ||
| var protocol = context.EnvironmentVariables.GetValueOrDefault("OTEL_EXPORTER_OTLP_PROTOCOL", ""); | ||
| var endpoint = protocol.ToString() == "http/protobuf" ? httpEndpoint : grpcEndpoint; | ||
|
|
||
| if (endpoint == null) | ||
martinjt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| logger.LogWarning("No {protocol} endpoint on the collector for {resourceName} to use", | ||
| protocol, resourceItem.Name); | ||
| return; | ||
| } | ||
|
|
||
| if (context.EnvironmentVariables.ContainsKey("OTEL_EXPORTER_OTLP_ENDPOINT")) | ||
| context.EnvironmentVariables.Remove("OTEL_EXPORTER_OTLP_ENDPOINT"); | ||
| context.EnvironmentVariables.Add("OTEL_EXPORTER_OTLP_ENDPOINT", endpoint.Url); | ||
| })); | ||
| } | ||
|
|
||
| return Task.CompletedTask; | ||
| } | ||
| } | ||
36 changes: 36 additions & 0 deletions
36
src/CommunityToolkit.Aspire.Hosting.OpenTelemetryCollector/OpenTelemetryCollectorSettings.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| namespace Aspire.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Settings for the OpenTelemetry Collector | ||
| /// </summary> | ||
| public class OpenTelemetryCollectorSettings | ||
| { | ||
| /// <summary> | ||
| /// The version of the collector, defaults to latest | ||
| /// </summary> | ||
| public string CollectorVersion { get; set; } = "latest"; | ||
aaronpowell marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// <summary> | ||
| /// The image of the collector, defaults to ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib | ||
| /// </summary> | ||
| public string CollectorImage { get; set; } = "ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib"; | ||
aaronpowell marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// <summary> | ||
| /// Force the default OTLP receivers in the collector to use HTTP even if Aspire is set to HTTPS | ||
| /// </summary> | ||
| public bool ForceNonSecureReceiver { get; set; } = false; | ||
|
|
||
| /// <summary> | ||
| /// Enable the gRPC endpoint on the collector container (requires the relevant collector config) | ||
| /// | ||
| /// Note: this will also setup SSL if Aspire is configured for HTTPS | ||
| /// </summary> | ||
| public bool EnableGrpcEndpoint { get; set; } = true; | ||
|
|
||
| /// <summary> | ||
| /// Enable the HTTP endpoint on the collector container (requires the relevant collector config) | ||
| /// | ||
| /// Note: this will also setup SSL if Aspire is configured for HTTPS | ||
| /// </summary> | ||
| public bool EnableHttpEndpoint { get; set; } = true; | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.