From 608332c772c0a5ad28dd0a4b7b753daecf07dc0f Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Mon, 4 Mar 2024 10:36:21 +0100 Subject: [PATCH] Enable metrics collection via provider (#43) * Remove code handling ELASTIC_ environment variables * Enable metrics provider --- ...xample.Elastic.OpenTelemetry.Worker.csproj | 1 + .../Program.cs | 2 +- .../Worker.cs | 44 +++++---- src/Elastic.OpenTelemetry/AgentBuilder.cs | 98 +++++++++++++++++-- .../ServiceCollectionExtensions.cs | 36 ++++++- .../ElasticDiagnosticLoggingObserver.cs | 20 ++++ .../ElasticOpenTelemetryDiagnostics.cs | 15 ++- .../Diagnostics/Payloads/AddSourcePayload.cs | 2 +- .../MeterBuilderProviderExtensions.cs | 18 ---- .../MeterProviderBuilderExtensions.cs | 21 ++++ .../Extensions/ResourceBuilderExtensions.cs | 2 +- ....cs => TracerProviderBuilderExtensions.cs} | 10 +- 12 files changed, 214 insertions(+), 55 deletions(-) delete mode 100644 src/Elastic.OpenTelemetry/Extensions/MeterBuilderProviderExtensions.cs create mode 100644 src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs rename src/Elastic.OpenTelemetry/Extensions/{TraceBuilderProviderExtensions.cs => TracerProviderBuilderExtensions.cs} (82%) diff --git a/examples/Example.Elastic.OpenTelemetry.Worker/Example.Elastic.OpenTelemetry.Worker.csproj b/examples/Example.Elastic.OpenTelemetry.Worker/Example.Elastic.OpenTelemetry.Worker.csproj index bd6c7ac..73e45cf 100644 --- a/examples/Example.Elastic.OpenTelemetry.Worker/Example.Elastic.OpenTelemetry.Worker.csproj +++ b/examples/Example.Elastic.OpenTelemetry.Worker/Example.Elastic.OpenTelemetry.Worker.csproj @@ -9,6 +9,7 @@ + diff --git a/examples/Example.Elastic.OpenTelemetry.Worker/Program.cs b/examples/Example.Elastic.OpenTelemetry.Worker/Program.cs index ca33f58..c1c8696 100644 --- a/examples/Example.Elastic.OpenTelemetry.Worker/Program.cs +++ b/examples/Example.Elastic.OpenTelemetry.Worker/Program.cs @@ -5,7 +5,7 @@ var builder = Host.CreateApplicationBuilder(args); -builder.AddElasticOpenTelemetry("CustomActivitySource"); +builder.AddElasticOpenTelemetry("CustomActivitySource", "CustomMeter"); builder.Services.AddHostedService(); diff --git a/examples/Example.Elastic.OpenTelemetry.Worker/Worker.cs b/examples/Example.Elastic.OpenTelemetry.Worker/Worker.cs index d36f378..3c8b7ad 100644 --- a/examples/Example.Elastic.OpenTelemetry.Worker/Worker.cs +++ b/examples/Example.Elastic.OpenTelemetry.Worker/Worker.cs @@ -2,40 +2,44 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information using System.Diagnostics; +using System.Diagnostics.Metrics; namespace Example.Elastic.OpenTelemetry.Worker; -public class Worker : BackgroundService +public class Worker(ILogger logger) : BackgroundService { - private readonly ILogger _logger; + private readonly ILogger _logger = logger; private static readonly HttpClient HttpClient = new(); private const string ActivitySourceName = "CustomActivitySource"; private static readonly ActivitySource ActivitySource = new(ActivitySourceName, "1.0.0"); - - public Worker(ILogger logger) => _logger = logger; + private static readonly Meter Meter = new("CustomMeter"); + private static readonly Counter Counter = Meter.CreateCounter("invocations", + null, null, [KeyValuePair.Create("label1", "value1")]); protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - while (!stoppingToken.IsCancellationRequested) - { - _logger.LogInformation("Sending request... "); + _logger.LogInformation("Sending request... "); - using (var activity = ActivitySource.StartActivity("DoingStuff", ActivityKind.Internal)) - { - activity?.SetTag("CustomTag", "TagValue"); + using (var activity = ActivitySource.StartActivity("DoingStuff", ActivityKind.Internal)) + { + activity?.SetTag("CustomTag", "TagValue"); + + if (Counter.Enabled) + Counter.Add(1); - await Task.Delay(100, stoppingToken); - var response = await HttpClient.GetAsync("http://elastic.co", stoppingToken); - await Task.Delay(50, stoppingToken); + _logger.LogInformation("Sending request... "); - if (response.StatusCode == System.Net.HttpStatusCode.OK) - activity?.SetStatus(ActivityStatusCode.Ok); - else - activity?.SetStatus(ActivityStatusCode.Error); - } + await Task.Delay(100, stoppingToken); + var response = await HttpClient.GetAsync("http://elastic.co", stoppingToken); + await Task.Delay(50, stoppingToken); - await Task.Delay(5000, stoppingToken); + if (response.StatusCode == System.Net.HttpStatusCode.OK) + activity?.SetStatus(ActivityStatusCode.Ok); + else + activity?.SetStatus(ActivityStatusCode.Error); } + + await Task.Delay(5000, stoppingToken); } -} +} \ No newline at end of file diff --git a/src/Elastic.OpenTelemetry/AgentBuilder.cs b/src/Elastic.OpenTelemetry/AgentBuilder.cs index 3a680e8..095f3b2 100644 --- a/src/Elastic.OpenTelemetry/AgentBuilder.cs +++ b/src/Elastic.OpenTelemetry/AgentBuilder.cs @@ -22,14 +22,9 @@ namespace Elastic.OpenTelemetry; /// public class AgentBuilder { - private readonly MeterProviderBuilder _meterProvider = - Sdk.CreateMeterProviderBuilder() - .AddProcessInstrumentation() - .AddRuntimeInstrumentation() - .AddHttpClientInstrumentation(); - private readonly string[] _activitySourceNames = []; private Action _tracerProviderBuilderAction = tpb => { }; + private Action _meterProviderBuilderAction = mpb => { }; private Action? _resourceBuilderAction = rb => { }; private Action? _otlpExporterConfiguration; private string? _otlpExporterName; @@ -144,6 +139,42 @@ public AgentBuilder ConfigureTracer(Action configureResourceBui return this; } + /// + /// TODO + /// + public AgentBuilder ConfigureMeter(params string[] activitySourceNames) + { + MeterInternal(null, activitySourceNames); + return this; + } + + /// + /// TODO + /// + public AgentBuilder ConfigureMeter(Action configureResourceBuilder) + { + MeterInternal(configureResourceBuilder, null); + return this; + } + + /// + /// TODO + /// + public AgentBuilder ConfigureMeter(Action configureResourceBuilder, params string[] activitySourceNames) + { + MeterInternal(configureResourceBuilder, activitySourceNames); + return this; + } + + /// + /// TODO + /// + public AgentBuilder ConfigureMeter(Action configureResourceBuilder, string activitySourceName) + { + MeterInternal(configureResourceBuilder, [activitySourceName]); + return this; + } + /// /// TODO /// @@ -161,6 +192,26 @@ public AgentBuilder ConfigureTracer(Action configure) return this; } + /// + /// TODO + /// + public AgentBuilder ConfigureMeter(Action configure) + { + ArgumentNullException.ThrowIfNull(configure); + _meterProviderBuilderAction += configure; + return this; + } + + private AgentBuilder MeterInternal(Action? configureResourceBuilder = null, string[]? activitySourceNames = null) + { + _resourceBuilderAction = configureResourceBuilder; + + if (activitySourceNames is not null) + _meterProviderBuilderAction += mpb => mpb.AddMeter(activitySourceNames); + + return this; + } + private AgentBuilder TracerInternal(Action? configureResourceBuilder = null, string[]? activitySourceNames = null) { _resourceBuilderAction = configureResourceBuilder; @@ -183,7 +234,13 @@ public IAgent Build() Log(AgentBuilderBuiltTracerProviderEvent); - var agent = tracerProvider is not null ? new Agent(_diagnosticSourceSubscription, tracerProvider) : new Agent(_diagnosticSourceSubscription); + var meterProviderBuilder = Sdk.CreateMeterProviderBuilder(); + MeterProviderBuilderAction.Invoke(meterProviderBuilder); + var meterProvider = meterProviderBuilder.Build(); + + Log(AgentBuilderBuiltMeterProviderEvent); + + var agent = new Agent(_diagnosticSourceSubscription, tracerProvider, meterProvider); Log(AgentBuilderBuiltAgentEvent); @@ -207,7 +264,8 @@ public IServiceCollection Register(IServiceCollection serviceCollection) .AddSingleton(new Agent(_diagnosticSourceSubscription)) .AddSingleton() .AddOpenTelemetry() - .WithTracing(TracerProviderBuilderAction); + .WithTracing(TracerProviderBuilderAction) + .WithMetrics(MeterProviderBuilderAction); Log(AgentBuilderRegisteredDistroServicesEvent); @@ -236,6 +294,30 @@ public IServiceCollection Register(IServiceCollection serviceCollection) tracerProviderBuilder.AddOtlpExporter(_otlpExporterName, _otlpExporterConfiguration); }; + private Action MeterProviderBuilderAction => + builder => + { + foreach (var source in _activitySourceNames) + builder.LogAndAddMeter(source); + + builder + .AddProcessInstrumentation() + .AddRuntimeInstrumentation() + .AddHttpClientInstrumentation(); + + var action = _resourceBuilderAction; + action += b => b.AddDistroAttributes(); + builder.ConfigureResource(action); + + _meterProviderBuilderAction?.Invoke(builder); + + builder.AddOtlpExporter(_otlpExporterName, o => + { + o.ExportProcessorType = ExportProcessorType.Simple; + o.Protocol = OtlpExportProtocol.HttpProtobuf; + }); + }; + /// /// TODO /// diff --git a/src/Elastic.OpenTelemetry/DependencyInjection/ServiceCollectionExtensions.cs b/src/Elastic.OpenTelemetry/DependencyInjection/ServiceCollectionExtensions.cs index 7ee3ee6..c571c33 100644 --- a/src/Elastic.OpenTelemetry/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Elastic.OpenTelemetry/DependencyInjection/ServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using Elastic.OpenTelemetry; using Microsoft.Extensions.Hosting; +using OpenTelemetry.Metrics; using OpenTelemetry.Trace; namespace Microsoft.Extensions.DependencyInjection; @@ -32,6 +33,23 @@ public static IHostApplicationBuilder AddElasticOpenTelemetry(this IHostApplicat return builder; } + /// + /// TODO + /// + /// + /// + /// + /// + public static IHostApplicationBuilder AddElasticOpenTelemetry( + this IHostApplicationBuilder builder, + Action? configureTracerProvider, + Action? configureMeterProvider) + { + builder.Services.AddElasticOpenTelemetry(configureTracerProvider, configureMeterProvider); + return builder; + } + + /// /// Adds the Elastic OpenTelemetry distribution to an application via the . /// @@ -54,7 +72,21 @@ public static IServiceCollection AddElasticOpenTelemetry(this IServiceCollection /// /// /// + /// /// - public static IServiceCollection AddElasticOpenTelemetry(this IServiceCollection serviceCollection, Action configureTracerProvider) => - new AgentBuilder().ConfigureTracer(configureTracerProvider).Register(serviceCollection); + public static IServiceCollection AddElasticOpenTelemetry( + this IServiceCollection serviceCollection, + Action? configureTracerProvider, + Action? configureMeterProvider) + { + var builder = new AgentBuilder(); + + if (configureTracerProvider is not null) + builder.ConfigureTracer(configureTracerProvider); + + if (configureMeterProvider is not null) + builder.ConfigureMeter(configureMeterProvider); + + return builder.Register(serviceCollection); + } } diff --git a/src/Elastic.OpenTelemetry/Diagnostics/ElasticDiagnosticLoggingObserver.cs b/src/Elastic.OpenTelemetry/Diagnostics/ElasticDiagnosticLoggingObserver.cs index bf9b714..d0b4159 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/ElasticDiagnosticLoggingObserver.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/ElasticDiagnosticLoggingObserver.cs @@ -24,6 +24,10 @@ public void OnNext(KeyValuePair data) AgentBuilderBuiltTracerProvider(data); break; + case ElasticOpenTelemetryDiagnostics.AgentBuilderBuiltMeterProviderEvent: + AgentBuilderBuiltMeterProvider(data); + break; + case ElasticOpenTelemetryDiagnostics.AgentBuilderBuiltAgentEvent: AgentBuilderBuiltAgent(data); break; @@ -52,6 +56,10 @@ public void OnNext(KeyValuePair data) SourceAdded(data); break; + case ElasticOpenTelemetryDiagnostics.MeterAddedEvent: + MeterAdded(data); + break; + default: if (data.Value is DiagnosticEvent diagnostic) _logFileWriter.LogUnhandledEvent(data.Key, diagnostic); @@ -70,6 +78,12 @@ void AgentBuilderBuiltTracerProvider(KeyValuePair data) _logFileWriter.LogAgentBuilderBuiltTracerProvider(diagnostic); } + void AgentBuilderBuiltMeterProvider(KeyValuePair data) + { + if (data.Value is DiagnosticEvent diagnostic) + _logFileWriter.LogAgentBuilderBuiltMeterProvider(diagnostic); + } + void AgentBuilderBuiltAgent(KeyValuePair data) { if (data.Value is DiagnosticEvent diagnostic) @@ -111,6 +125,12 @@ void SourceAdded(KeyValuePair data) if (data.Value is DiagnosticEvent diagnostic) _logFileWriter.LogSourceAdded(diagnostic); } + + void MeterAdded(KeyValuePair data) + { + if (data.Value is DiagnosticEvent diagnostic) + _logFileWriter.LogMeterAdded(diagnostic); + } } public void OnCompleted() { } diff --git a/src/Elastic.OpenTelemetry/Diagnostics/ElasticOpenTelemetryDiagnostics.cs b/src/Elastic.OpenTelemetry/Diagnostics/ElasticOpenTelemetryDiagnostics.cs index 7b66ad1..539c51f 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/ElasticOpenTelemetryDiagnostics.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/ElasticOpenTelemetryDiagnostics.cs @@ -39,6 +39,8 @@ public static void Log(string name, Func createDiagnosticEvent) public const string AgentBuilderBuiltTracerProviderEvent = "AgentBuilderBuiltTracerProvider"; + public const string AgentBuilderBuiltMeterProviderEvent = "AgentBuilderBuiltMeterProvider"; + public const string AgentBuilderRegisteredDistroServicesEvent = "RegisteredDistroServices"; public const string AgentBuilderBuiltAgentEvent = "AgentBuilderBuiltAgent"; @@ -49,6 +51,8 @@ public static void Log(string name, Func createDiagnosticEvent) public const string SourceAddedEvent = "SourceAdded"; + public const string MeterAddedEvent = "MeterAdded"; + public const string AgentBuildCalledMultipleTimesEvent = "AgentBuildCalledMultipleTimes"; public const string AgentSetAgentCalledMultipleTimesEvent = "AgentSetAgentCalledMultipleTimes"; @@ -65,6 +69,9 @@ public static void LogAgentBuilderInitialized(this LogFileWriter logFileWriter, public static void LogAgentBuilderBuiltTracerProvider(this LogFileWriter logFileWriter, DiagnosticEvent diagnostic) => logFileWriter.WriteInfoLogLine(diagnostic, "AgentBuilder built TracerProvider."); + public static void LogAgentBuilderBuiltMeterProvider(this LogFileWriter logFileWriter, DiagnosticEvent diagnostic) => + logFileWriter.WriteInfoLogLine(diagnostic, "AgentBuilder built MeterProvider."); + public static void LogAgentBuilderBuiltAgent(this LogFileWriter logFileWriter, DiagnosticEvent diagnostic) => logFileWriter.WriteInfoLogLine(diagnostic, "AgentBuilder built Agent."); @@ -91,7 +98,13 @@ public static void LogProcessorAdded(this LogFileWriter logFileWriter, Diagnosti public static void LogSourceAdded(this LogFileWriter logFileWriter, DiagnosticEvent diagnostic) { - var message = $"Added '{diagnostic.Data.ActivitySourceName}' ActivitySource to '{diagnostic.Data.BuilderType.Name}'."; + var message = $"Added '{diagnostic.Data.Name}' ActivitySource to '{diagnostic.Data.BuilderType.Name}'."; + logFileWriter.WriteInfoLogLine(diagnostic, message); + } + + public static void LogMeterAdded(this LogFileWriter logFileWriter, DiagnosticEvent diagnostic) + { + var message = $"Added '{diagnostic.Data.Name}' Meter to '{diagnostic.Data.BuilderType.Name}'."; logFileWriter.WriteInfoLogLine(diagnostic, message); } diff --git a/src/Elastic.OpenTelemetry/Diagnostics/Payloads/AddSourcePayload.cs b/src/Elastic.OpenTelemetry/Diagnostics/Payloads/AddSourcePayload.cs index 8ddb3d8..6741a7d 100644 --- a/src/Elastic.OpenTelemetry/Diagnostics/Payloads/AddSourcePayload.cs +++ b/src/Elastic.OpenTelemetry/Diagnostics/Payloads/AddSourcePayload.cs @@ -3,4 +3,4 @@ // See the LICENSE file in the project root for more information namespace Elastic.OpenTelemetry.Diagnostics; -internal readonly record struct AddSourcePayload(string ActivitySourceName, Type BuilderType); +internal readonly record struct AddSourcePayload(string Name, Type BuilderType); diff --git a/src/Elastic.OpenTelemetry/Extensions/MeterBuilderProviderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/MeterBuilderProviderExtensions.cs deleted file mode 100644 index 0932b7c..0000000 --- a/src/Elastic.OpenTelemetry/Extensions/MeterBuilderProviderExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to Elasticsearch B.V under one or more agreements. -// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information -using OpenTelemetry.Metrics; - -namespace Elastic.OpenTelemetry.Extensions; - -/// Provides Elastic APM extensions to -public static class MeterBuilderProviderExtensions -{ - //TODO binder source generator on Build() to make it automatic? - /// - /// TODO - /// - public static MeterProviderBuilder AddElastic(this MeterProviderBuilder builder) => - builder - .AddMeter("Elastic.OpenTelemetry"); -} diff --git a/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs new file mode 100644 index 0000000..848de8f --- /dev/null +++ b/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs @@ -0,0 +1,21 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information +using Elastic.OpenTelemetry.Diagnostics; +using OpenTelemetry.Metrics; + +using static Elastic.OpenTelemetry.Diagnostics.ElasticOpenTelemetryDiagnostics; + +namespace Elastic.OpenTelemetry.Extensions; + +/// +/// Provides Elastic APM extensions to . +/// +public static class MeterProviderBuilderExtensions +{ + internal static MeterProviderBuilder LogAndAddMeter(this MeterProviderBuilder builder, string meterName) + { + Log(MeterAddedEvent, () => new DiagnosticEvent(new(meterName, builder.GetType()))); + return builder.AddMeter(meterName); + } +} diff --git a/src/Elastic.OpenTelemetry/Extensions/ResourceBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/ResourceBuilderExtensions.cs index 3ba830d..5d1abf5 100644 --- a/src/Elastic.OpenTelemetry/Extensions/ResourceBuilderExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/ResourceBuilderExtensions.cs @@ -1,7 +1,7 @@ // Licensed to Elasticsearch B.V under one or more agreements. // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -using Elastic.OpenTelemetry.SemanticConventions; +using Elastic.OpenTelemetry.SemanticConventions; using OpenTelemetry.Resources; diff --git a/src/Elastic.OpenTelemetry/Extensions/TraceBuilderProviderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs similarity index 82% rename from src/Elastic.OpenTelemetry/Extensions/TraceBuilderProviderExtensions.cs rename to src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs index 99a99bd..47576d8 100644 --- a/src/Elastic.OpenTelemetry/Extensions/TraceBuilderProviderExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs @@ -11,11 +11,15 @@ namespace Elastic.OpenTelemetry.Extensions; -/// Provides Elastic APM extensions to -public static class TraceBuilderProviderExtensions +/// +/// Provides Elastic APM extensions to . +/// +public static class TracerProviderBuilderExtensions { //TODO binder source generator on Build() to make it automatic? - /// Include Elastic APM Trace Processors to ensure data is enriched and extended. + /// + /// Include Elastic APM Trace Processors to ensure data is enriched and extended. + /// public static TracerProviderBuilder AddElasticProcessors(this TracerProviderBuilder builder) => builder.LogAndAddProcessor(new TransactionIdProcessor());