Skip to content

Commit

Permalink
Introduce AgentBuilderOptions (#50)
Browse files Browse the repository at this point in the history
* Introduce AgentBuilderOptions

Sadly we can not defer OltpExport to AgentBuilder.Build() in *all* IHostingEnvironments.

Atleast under WebApplicationHosts the IHostedLifecycleSerivce.StartingAsync happens on

app.Run() at which stage the IServiceCollection is marked as readonly.

To that end we now expose overloads to AddOpenTelemetry taking AgentBuilderOptions.

These are typically only needed for expert level control and not part of the documented getting started
approach.

This PR also addresses all warnings in the solution (branch started off for just that sorry!)

* remove recursive SkipOtlpRegistration extension method

* fix double logger initialization in logger tests
  • Loading branch information
Mpdreamz authored Mar 6, 2024
1 parent d20cc44 commit 1d8bd3f
Show file tree
Hide file tree
Showing 25 changed files with 193 additions and 208 deletions.
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ resharper_csharp_accessor_owner_body=expression_body

resharper_redundant_case_label_highlighting=do_not_show
resharper_redundant_argument_default_value_highlighting=do_not_show
resharper_explicit_caller_info_argument_highlighting=hint

dotnet_diagnostic.IDE0057.severity = none

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public async Task<IActionResult> Index()

await Task.Delay(100);

using var childActivity = activity?.Source.StartActivity("childActivity", ActivityKind.Internal);
using var childActivity = activity?.Source.StartActivity(ActivityKind.Internal);
await Task.Delay(200);

return View();
Expand All @@ -34,7 +34,7 @@ public async Task<IActionResult> Fail()

await Task.Delay(100);

using var childActivity = activity?.Source.StartActivity("childActivity", ActivityKind.Internal);
using var childActivity = activity?.Source.StartActivity(ActivityKind.Internal);
await Task.Delay(200);

throw new Exception("Random failure");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// 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.Net;
using Example.Elastic.OpenTelemetry.AspNetCore.Models;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;

namespace Example.Elastic.OpenTelemetry.AspNetCore.Controllers;
Expand All @@ -17,19 +17,15 @@ public async Task<IActionResult> Index()
{
using var client = httpClientFactory.CreateClient();

var activityFeature = HttpContext.Features.Get<IHttpActivityFeature>();

// ReSharper disable once ExplicitCallerInfoArgument
using var activity = ActivitySource.StartActivity("DoingStuff", ActivityKind.Internal);
activity?.SetTag("CustomTag", "TagValue");

await Task.Delay(100);
var response = await client.GetAsync("http://elastic.co");
await Task.Delay(50);

if (response.StatusCode == System.Net.HttpStatusCode.OK)
activity?.SetStatus(ActivityStatusCode.Ok);
else
activity?.SetStatus(ActivityStatusCode.Error);
activity?.SetStatus(response.StatusCode == HttpStatusCode.OK ? ActivityStatusCode.Ok : ActivityStatusCode.Error);

return View();
}
Expand Down
8 changes: 0 additions & 8 deletions examples/Example.Elastic.OpenTelemetry.Worker/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// 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;
using Example.Elastic.OpenTelemetry.Worker;
using OpenTelemetry;
using OpenTelemetry.Metrics;
Expand All @@ -11,13 +10,6 @@

var builder = Host.CreateApplicationBuilder(args);

/*
* appBuilder.Services.AddOpenTelemetry()
.ConfigureResource(builder => builder.AddService(serviceName: "MyService"))
.WithTracing(builder => builder.AddConsoleExporter())
.WithMetrics(builder => builder.AddConsoleExporter());
*/

builder.Services.AddElasticOpenTelemetry(Worker.ActivitySourceName, "CustomMeter")
.ConfigureResource(r => r.AddService(serviceName: "MyService"))
.WithTracing(t => t.AddConsoleExporter())
Expand Down
2 changes: 1 addition & 1 deletion src/Elastic.OpenTelemetry/Agent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ private static string ParseAssemblyInformationalVersion(string? informationalVer
* The following parts are optional: pre-release label, pre-release version, git height, Git SHA of current commit
*/

var indexOfPlusSign = informationalVersion!.IndexOf('+');
var indexOfPlusSign = informationalVersion.IndexOf('+');
return indexOfPlusSign > 0
? informationalVersion[..indexOfPlusSign]
: informationalVersion;
Expand Down
32 changes: 0 additions & 32 deletions src/Elastic.OpenTelemetry/AgentBuilder.Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

Expand All @@ -16,14 +15,6 @@ namespace Elastic.OpenTelemetry;
/// <summary> TODO </summary>
public static class OpenTelemetryBuilderExtensions
{
/// <summary> TODO </summary>
public static IOpenTelemetryBuilder SkipOtlpExporter(this IOpenTelemetryBuilder builder)
{
if (builder is not AgentBuilder agentBuilder) return builder;

return agentBuilder.SkipOtlpExporter();
}

/// <summary> TODO </summary>
public static IOpenTelemetryBuilder WithLogger(this IOpenTelemetryBuilder builder, ILogger logger)
{
Expand All @@ -50,29 +41,6 @@ public static IAgent Build(this IOpenTelemetryBuilder builder, ILogger? logger =

log.SetAdditionalLogger(logger);

var openTelemetry =
Microsoft.Extensions.DependencyInjection.OpenTelemetryServicesExtensions.AddOpenTelemetry(agentBuilder.Services);

openTelemetry
.WithTracing(tracing =>
{
if (!agentBuilder.SkipOtlpRegistration)
tracing.AddOtlpExporter(agentBuilder.OtlpExporterName, agentBuilder.OtlpExporterConfiguration);
log.LogAgentBuilderBuiltTracerProvider();
})
.WithMetrics(metrics =>
{
if (!agentBuilder.SkipOtlpRegistration)
{
metrics.AddOtlpExporter(agentBuilder.OtlpExporterName, o =>
{
o.ExportProcessorType = ExportProcessorType.Simple;
o.Protocol = OtlpExportProtocol.HttpProtobuf;
});
}
log.LogAgentBuilderBuiltMeterProvider();
});

var sp = serviceProvider ?? agentBuilder.Services.BuildServiceProvider();
var tracerProvider = sp.GetService<TracerProvider>()!;
var meterProvider = sp.GetService<MeterProvider>()!;
Expand Down
97 changes: 68 additions & 29 deletions src/Elastic.OpenTelemetry/AgentBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,83 @@
using Elastic.OpenTelemetry.Diagnostics;
using Elastic.OpenTelemetry.Diagnostics.Logging;
using Elastic.OpenTelemetry.Extensions;
using Elastic.OpenTelemetry.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

namespace Elastic.OpenTelemetry;


/// <summary>
/// Expert options to provide to <see cref="AgentBuilder"/> to control its initial OpenTelemetry registration
/// </summary>
public record AgentBuilderOptions
{
/// <summary>
/// Provide an additional logger to the internal file logger.
/// <para>
/// The agent will always log to file if a Path is provided using the <c>ELASTIC_OTEL_LOG_DIRECTORY</c>
/// environment variable.</para>
/// </summary>
public ILogger? Logger { get; init; }

/// <summary>
/// Provides an <see cref="IServiceCollection"/> to register the agent into.
/// If null a new local instance will be used.
/// </summary>
public IServiceCollection? Services { get; init; }

/// <summary>
/// The initial activity sources to listen to.
/// <para>>These can always later be amended with <see cref="TracerProviderBuilder.AddSource"/></para>
/// </summary>
public string[] ActivitySources { get; init; } = [];

/// <summary>
/// Stops <see cref="AgentBuilder"/> to register OLTP exporters, useful for testing scenarios
/// </summary>
public bool SkipOtlpExporter { get; init; }

/// <summary>
/// Optional name which is used when retrieving OTLP options.
/// </summary>
public string? OtlpExporterName { get; init; }
}

/// <summary>
/// Supports building <see cref="IAgent"/> instances which include Elastic defaults, but can also be customised.
/// </summary>
public class AgentBuilder : IOpenTelemetryBuilder
{
private bool _skipOtlpRegistration;

internal Action<OtlpExporterOptions>? OtlpExporterConfiguration { get; private set; }
internal string? OtlpExporterName { get; private set; }
internal bool SkipOtlpRegistration => _skipOtlpRegistration;
internal AgentCompositeLogger Logger { get; }
internal LoggingEventListener EventListener { get; }

/// <inheritdoc cref="IOpenTelemetryBuilder.Services"/>
public IServiceCollection Services { get; }

/// <summary> TODO </summary>
public AgentBuilder(params string[] activitySourceNames) : this(null, null, activitySourceNames) { }
public AgentBuilder(params string[] activitySourceNames) : this(new AgentBuilderOptions
{
ActivitySources = activitySourceNames
}) { }

/// <summary> TODO </summary>
public AgentBuilder(ILogger? logger = null, IServiceCollection? services = null, params string[] activitySourceNames)
public AgentBuilder(AgentBuilderOptions options)
{
Logger = new AgentCompositeLogger(logger);
Logger = new AgentCompositeLogger(options.Logger);

// Enables logging of OpenTelemetry-SDK event source events
EventListener = new LoggingEventListener(Logger);

Logger.LogAgentPreamble();
Logger.LogAgentBuilderInitialized(Environment.NewLine, new StackTrace(true));
Services = services ?? new ServiceCollection();
Services = options.Services ?? new ServiceCollection();

if (services != null)
if (options.Services != null)
Services.AddHostedService<ElasticOtelDistroService>();

Services.AddSingleton(this);
Expand All @@ -60,7 +95,7 @@ public AgentBuilder(ILogger? logger = null, IServiceCollection? services = null,
{
tracing.ConfigureResource(r => r.AddDistroAttributes());
foreach (var source in activitySourceNames)
foreach (var source in options.ActivitySources)
tracing.LogAndAddSource(source, Logger);
tracing
Expand All @@ -74,7 +109,7 @@ public AgentBuilder(ILogger? logger = null, IServiceCollection? services = null,
{
metrics.ConfigureResource(r => r.AddDistroAttributes());
foreach (var source in activitySourceNames)
foreach (var source in options.ActivitySources)
{
Logger.LogMeterAdded(source, metrics.GetType().Name);
metrics.AddMeter(source);
Expand All @@ -86,24 +121,28 @@ public AgentBuilder(ILogger? logger = null, IServiceCollection? services = null,
.AddRuntimeInstrumentation()
.AddHttpClientInstrumentation();
});
Logger.LogAgentBuilderRegisteredServices();
}

/// <summary> TODO </summary>
public AgentBuilder SkipOtlpExporter()
{
_skipOtlpRegistration = true;
return this;
}

openTelemetry
.WithTracing(tracing =>
{
if (!options.SkipOtlpExporter)
tracing.AddOtlpExporter(options.OtlpExporterName, _ => { });
Logger.LogAgentBuilderBuiltTracerProvider();
})
.WithMetrics(metrics =>
{
if (!options.SkipOtlpExporter)
{
metrics.AddOtlpExporter(options.OtlpExporterName, o =>
{
o.ExportProcessorType = ExportProcessorType.Simple;
o.Protocol = OtlpExportProtocol.HttpProtobuf;
});
}
Logger.LogAgentBuilderBuiltMeterProvider();
});

/// <summary>
/// TODO
/// </summary>
public void ConfigureOtlpExporter(Action<OtlpExporterOptions> configure, string? name = null)
{
OtlpExporterConfiguration = configure ?? throw new ArgumentNullException(nameof(configure));
OtlpExporterName = name;
Logger.LogAgentBuilderRegisteredServices();
}
}

Expand Down

This file was deleted.

Loading

0 comments on commit 1d8bd3f

Please sign in to comment.