-
Notifications
You must be signed in to change notification settings - Fork 934
Expand file tree
/
Copy pathOtlpConfigurationExtensions.cs
More file actions
195 lines (168 loc) · 10.9 KB
/
Copy pathOtlpConfigurationExtensions.cs
File metadata and controls
195 lines (168 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Dcp;
using Aspire.Hosting.Dcp.Model;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Aspire.Hosting;
/// <summary>
/// Provides extension methods for configuring OpenTelemetry in projects using environment variables.
/// </summary>
public static class OtlpConfigurationExtensions
{
private const string DashboardOtlpUrlDefaultValue = "http://localhost:18889";
/// <summary>
/// Configures OpenTelemetry in projects using environment variables.
/// </summary>
/// <param name="resource">The resource to add annotations to.</param>
/// <param name="configuration">The configuration to use for the OTLP exporter endpoint URL.</param>
/// <param name="environment">The host environment to check if the application is running in development mode.</param>
public static void AddOtlpEnvironment(IResource resource, IConfiguration configuration, IHostEnvironment environment)
{
ArgumentNullException.ThrowIfNull(resource);
ArgumentNullException.ThrowIfNull(configuration);
ArgumentNullException.ThrowIfNull(environment);
// Add annotation to mark this resource as having OTLP exporter configured
resource.Annotations.Add(new OtlpExporterAnnotation());
RegisterOtlpEnvironment(resource, configuration, environment);
}
/// <summary>
/// Configures OpenTelemetry in projects using environment variables.
/// </summary>
/// <param name="resource">The resource to add annotations to.</param>
/// <param name="configuration">The configuration to use for the OTLP exporter endpoint URL.</param>
/// <param name="environment">The host environment to check if the application is running in development mode.</param>
/// <param name="protocol">The protocol to use for the OTLP exporter. If not set, it will try gRPC then Http.</param>
public static void AddOtlpEnvironment(IResource resource, IConfiguration configuration, IHostEnvironment environment, OtlpProtocol protocol)
{
ArgumentNullException.ThrowIfNull(resource);
ArgumentNullException.ThrowIfNull(configuration);
ArgumentNullException.ThrowIfNull(environment);
// Add annotation to mark this resource as having OTLP exporter configured with a required protocol
resource.Annotations.Add(new OtlpExporterAnnotation { RequiredProtocol = protocol });
RegisterOtlpEnvironment(resource, configuration, environment);
}
private static void RegisterOtlpEnvironment(IResource resource, IConfiguration configuration, IHostEnvironment environment)
{
// Configure OpenTelemetry in projects using environment variables.
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md
resource.Annotations.Add(new EnvironmentCallbackAnnotation(async context =>
{
if (context.ExecutionContext.IsPublishMode)
{
// REVIEW: Do we want to set references to an imaginary otlp provider as a requirement?
return;
}
if (!resource.TryGetLastAnnotation<OtlpExporterAnnotation>(out var otlpExporterAnnotation))
{
return;
}
SetOtel(context, configuration, otlpExporterAnnotation.RequiredProtocol);
// Set the service name and instance id to the resource name and UID. Values are injected by DCP.
var dcpDependencyCheckService = context.ExecutionContext.ServiceProvider.GetRequiredService<IDcpDependencyCheckService>();
var dcpInfo = await dcpDependencyCheckService.GetDcpInfoAsync(cancellationToken: context.CancellationToken).ConfigureAwait(false);
context.EnvironmentVariables["OTEL_RESOURCE_ATTRIBUTES"] = "service.instance.id={{- index .Annotations \"" + CustomResource.OtelServiceInstanceIdAnnotation + "\" -}}";
context.EnvironmentVariables["OTEL_SERVICE_NAME"] = "{{- index .Annotations \"" + CustomResource.OtelServiceNameAnnotation + "\" -}}";
if (configuration["AppHost:OtlpApiKey"] is { } otlpApiKey)
{
context.EnvironmentVariables["OTEL_EXPORTER_OTLP_HEADERS"] = $"x-otlp-api-key={otlpApiKey}";
}
// Configure OTLP to quickly provide all data with a small delay in development.
if (environment.IsDevelopment())
{
// Set a small batch schedule delay in development.
// This reduces the delay that OTLP exporter waits to sends telemetry and makes the dashboard telemetry pages responsive.
var value = "1000"; // milliseconds
context.EnvironmentVariables["OTEL_BLRP_SCHEDULE_DELAY"] = value;
context.EnvironmentVariables["OTEL_BSP_SCHEDULE_DELAY"] = value;
context.EnvironmentVariables["OTEL_METRIC_EXPORT_INTERVAL"] = value;
// Configure trace sampler to send all traces to the dashboard.
context.EnvironmentVariables["OTEL_TRACES_SAMPLER"] = "always_on";
// Configure metrics to include exemplars.
context.EnvironmentVariables["OTEL_METRICS_EXEMPLAR_FILTER"] = "trace_based";
// Output sensitive message content for GenAI.
// A convention for libraries that output GenAI telemetry is to use `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` env var.
// See:
// - https://opentelemetry.io/blog/2024/otel-generative-ai/
// - https://github.com/search?q=org%3Aopen-telemetry+OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT&type=code
context.EnvironmentVariables["OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"] = "true";
}
}));
static void SetOtel(EnvironmentCallbackContext context, IConfiguration configuration, OtlpProtocol? requiredProtocol)
{
var dashboardOtlpGrpcUrl = configuration.GetString(KnownConfigNames.DashboardOtlpGrpcEndpointUrl, KnownConfigNames.Legacy.DashboardOtlpGrpcEndpointUrl);
var dashboardOtlpHttpUrl = configuration.GetString(KnownConfigNames.DashboardOtlpHttpEndpointUrl, KnownConfigNames.Legacy.DashboardOtlpHttpEndpointUrl);
// Check if a specific protocol is required by the annotation
if (requiredProtocol is OtlpProtocol.Grpc)
{
SetOtelEndpointAndProtocol(context.EnvironmentVariables, dashboardOtlpGrpcUrl ?? DashboardOtlpUrlDefaultValue, "grpc");
}
else if (requiredProtocol is OtlpProtocol.HttpProtobuf)
{
SetOtelEndpointAndProtocol(context.EnvironmentVariables, dashboardOtlpHttpUrl ?? throw new InvalidOperationException("OtlpExporter is configured to require http/protobuf, but no endpoint was configured for ASPIRE_DASHBOARD_OTLP_HTTP_ENDPOINT_URL"), "http/protobuf");
}
else
{
// No specific protocol required, use the existing preference logic
// The dashboard can support OTLP/gRPC and OTLP/HTTP endpoints at the same time, but it can
// only tell resources about one of the endpoints via environment variables.
// If both OTLP/gRPC and OTLP/HTTP are available then prefer gRPC.
if (dashboardOtlpGrpcUrl is not null)
{
SetOtelEndpointAndProtocol(context.EnvironmentVariables, dashboardOtlpGrpcUrl, "grpc");
}
else if (dashboardOtlpHttpUrl is not null)
{
SetOtelEndpointAndProtocol(context.EnvironmentVariables, dashboardOtlpHttpUrl, "http/protobuf");
}
else
{
// No endpoints provided to host. Use default value for URL.
SetOtelEndpointAndProtocol(context.EnvironmentVariables, DashboardOtlpUrlDefaultValue, "grpc");
}
}
}
static void SetOtelEndpointAndProtocol(Dictionary<string, object> environmentVariables, string url, string protocol)
{
environmentVariables["OTEL_EXPORTER_OTLP_ENDPOINT"] = new HostUrl(url);
environmentVariables["OTEL_EXPORTER_OTLP_PROTOCOL"] = protocol;
}
}
/// <summary>
/// Injects the appropriate environment variables to allow the resource to enable sending telemetry to the dashboard.
/// <list type="number">
/// <item>It sets the OTLP endpoint to the value of the <c>ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL</c> environment variable.</item>
/// <item>It sets the service name and instance id to the resource name and UID. Values are injected by the orchestrator.</item>
/// <item>It sets a small batch schedule delay in development. This reduces the delay that OTLP exporter waits to sends telemetry and makes the dashboard telemetry pages responsive.</item>
/// </list>
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<T> WithOtlpExporter<T>(this IResourceBuilder<T> builder) where T : IResourceWithEnvironment
{
ArgumentNullException.ThrowIfNull(builder);
AddOtlpEnvironment(builder.Resource, builder.ApplicationBuilder.Configuration, builder.ApplicationBuilder.Environment);
return builder;
}
/// <summary>
/// Injects the appropriate environment variables to allow the resource to enable sending telemetry to the dashboard.
/// <list type="number">
/// <item>It sets the OTLP endpoint to the value of the <c>ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL</c> environment variable.</item>
/// <item>It sets the service name and instance id to the resource name and UID. Values are injected by the orchestrator.</item>
/// <item>It sets a small batch schedule delay in development. This reduces the delay that OTLP exporter waits to sends telemetry and makes the dashboard telemetry pages responsive.</item>
/// </list>
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="protocol">The protocol to use for the OTLP exporter. If not set, it will try gRPC then Http.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<T> WithOtlpExporter<T>(this IResourceBuilder<T> builder, OtlpProtocol protocol) where T : IResourceWithEnvironment
{
ArgumentNullException.ThrowIfNull(builder);
AddOtlpEnvironment(builder.Resource, builder.ApplicationBuilder.Configuration, builder.ApplicationBuilder.Environment, protocol);
return builder;
}
}