Skip to content

Commit

Permalink
Move our AgentBuilder to IOpenTelemetryBuilder (#47)
Browse files Browse the repository at this point in the history
* Move our AgentBuilder to IOpenTelemetryBuilder

This is not yet released see open-telemetry/opentelemetry-dotnet#5265 for more background.

For now we include a temporary copy with hacks to call internal bits.

This allows AgentBuilder to be a native opentelemetry builder and we'd inherit all extension methods from the OpenTelemetry community

* clean up AddElasticOpenTelemetry()
  • Loading branch information
Mpdreamz committed Mar 5, 2024
1 parent 37ad73e commit f2f6f6c
Show file tree
Hide file tree
Showing 16 changed files with 393 additions and 386 deletions.
4 changes: 3 additions & 1 deletion Elastic.OpenTelemetry.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -585,4 +585,6 @@ See the LICENSE file in the project root for more information</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Faas/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Instrumentations/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mountinfo/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nonsampled/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nonsampled/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=otel/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Otlp/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
4 changes: 3 additions & 1 deletion examples/Example.Elastic.OpenTelemetry.AspNetCore/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
// Add services to the container.
builder.Services
.AddHttpClient()
.AddElasticOpenTelemetryForAspNetCore(HomeController.ActivitySourceName)
.AddElasticOpenTelemetryForAspNetCore(HomeController.ActivitySourceName);

builder.Services
.AddControllersWithViews();

var app = builder.Build();
Expand Down
18 changes: 17 additions & 1 deletion examples/Example.Elastic.OpenTelemetry.Worker/Program.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
// 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;
using Example.Elastic.OpenTelemetry.Worker;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddElasticOpenTelemetry(Worker.ActivitySourceName, "CustomMeter");
/*
* 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())
.WithMetrics(m => m.AddConsoleExporter());

builder.Services.AddHostedService<Worker>();

Expand Down
52 changes: 27 additions & 25 deletions examples/Example.Elastic.OpenTelemetry/Usage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,36 @@ public static async Task BasicBuilderUsageAsync()
// Build an agent by creating and using an agent builder, adding a single source (for traces and metrics) defined in this sample application.
await using var agent = new AgentBuilder(ActivitySourceName).Build();

// Build an agent by creating and using an agent builder, adding a single source for the app just to the tracer.
//using var agent = new AgentBuilder().AddTracerSource(ActivitySourceName).Build();

// This example adds the application activity source and fully customises the resource
//using var agent = new AgentBuilder(ActivitySourceName)
// .ConfigureTracer(b => b.Clear().AddService("CustomServiceName", serviceVersion: "2.2.2"))
// .Build();

//using var agent = new AgentBuilder()
// .AddTracerSource(ActivitySourceName) // Intentionally duplicating to test the effect
// .ConfigureTracer(tpb => tpb
// .ConfigureResource(rb => rb.AddService("TracerProviderBuilder", "3.3.3"))
// .AddRedisInstrumentation() // This can currently only be achieved using this overload or adding Elastic processors to the TPB (as below)
// .AddSource(ActivitySourceName)
// .AddConsoleExporter())
// .Build();
await using var agent3 = new AgentBuilder(ActivitySourceName)
.WithTracing(b => b.ConfigureResource(r => r.Clear().AddService("CustomServiceName", serviceVersion: "2.2.2")))
.Build();

await using var agent4 = new AgentBuilder()
.WithTracing(t => t
.ConfigureResource(rb => rb.AddService("TracerProviderBuilder", "3.3.3"))
.AddRedisInstrumentation() // This can currently only be achieved using this overload or adding Elastic processors to the TPB (as below)
.AddSource(ActivitySourceName)
.AddConsoleExporter()
)
.WithTracing(tpb => tpb
.ConfigureResource(rb => rb.AddService("TracerProviderBuilder", "3.3.3"))
.AddRedisInstrumentation() // This can currently only be achieved using this overload or adding Elastic processors to the TPB (as below)
.AddSource(ActivitySourceName)
.AddConsoleExporter())
.Build();

//This is the most flexible approach for a consumer as they can include our processor(s) and exporter(s)
//using var tracerProvider = Sdk.CreateTracerProviderBuilder()
// .AddSource(ActivitySourceName)
// .ConfigureResource(resource =>
// resource.AddService(
// serviceName: "OtelSdkApp",
// serviceVersion: "1.0.0"))
// .AddConsoleExporter()
// .AddElasticProcessors()
// .AddElasticOtlpExporter()
// .Build();
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource(ActivitySourceName)
.ConfigureResource(resource =>
resource.AddService(
serviceName: "OtelSdkApp",
serviceVersion: "1.0.0"))
.AddConsoleExporter()
.AddElasticProcessors()
//.AddElasticOtlpExporter()
.Build();

await DoStuffAsync();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
// 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;
using OpenTelemetry.Trace;

namespace Elastic.OpenTelemetry.AspNetCore;

/// <summary>
///
///
/// </summary>
public static class AgentBuilderExtensions
{
/// <summary>
///
///
/// </summary>
/// <param name="agentBuilder"></param>
public static AgentBuilder AddAspNetCore(this AgentBuilder agentBuilder) => agentBuilder.ConfigureTracer(tpb => tpb.AddAspNetCoreInstrumentation());
public static IOpenTelemetryBuilder AddAspNetCore(this AgentBuilder agentBuilder) => agentBuilder.WithTracing(tpb => tpb.AddAspNetCoreInstrumentation());
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information
using Elastic.OpenTelemetry;
using Elastic.OpenTelemetry.AspNetCore;
using OpenTelemetry;
using OpenTelemetry.Trace;

namespace Microsoft.Extensions.DependencyInjection;
Expand All @@ -17,24 +18,15 @@ public static class ServiceCollectionExtensions
/// </summary>
/// <param name="serviceCollection">TODO</param>
/// <returns>TODO</returns>
public static IServiceCollection AddElasticOpenTelemetryForAspNetCore(this IServiceCollection serviceCollection) =>
new AgentBuilder().AddAspNetCore().Register(serviceCollection);
public static IOpenTelemetryBuilder AddElasticOpenTelemetryForAspNetCore(this IServiceCollection serviceCollection) =>
new AgentBuilder(services: serviceCollection).AddAspNetCore();

/// <summary>
/// TODO
/// </summary>
/// <param name="serviceCollection"></param>
/// <param name="activitySourceNames"></param>
/// <returns></returns>
public static IServiceCollection AddElasticOpenTelemetryForAspNetCore(this IServiceCollection serviceCollection, params string[] activitySourceNames) =>
new AgentBuilder(activitySourceNames).AddAspNetCore().Register(serviceCollection);

/// <summary>
/// TODO
/// </summary>
/// <param name="serviceCollection"></param>
/// <param name="configureTracerProvider"></param>
/// <returns></returns>
public static IServiceCollection AddElasticOpenTelemetryForAspNetCore(this IServiceCollection serviceCollection, Action<TracerProviderBuilder> configureTracerProvider) =>
new AgentBuilder().AddAspNetCore().ConfigureTracer(configureTracerProvider).Register(serviceCollection);
public static IOpenTelemetryBuilder AddElasticOpenTelemetryForAspNetCore(this IServiceCollection serviceCollection, params string[] activitySourceNames) =>
new AgentBuilder(null, serviceCollection, activitySourceNames).AddAspNetCore();
}
96 changes: 96 additions & 0 deletions src/Elastic.OpenTelemetry/AgentBuilder.Build.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// 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 Elastic.OpenTelemetry.Diagnostics.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

namespace Elastic.OpenTelemetry;

/// <summary> TODO </summary>
public static class OpenTelemetryBuilderExtensions
{
/// <summary>
/// Build an instance of <see cref="IAgent"/>.
/// </summary>
/// <returns>A new instance of <see cref="IAgent"/>.</returns>
public static IAgent Build(this IOpenTelemetryBuilder builder, ILogger? logger = null, IServiceProvider? serviceProvider = null)
{
// this happens if someone calls Build() while using IServiceCollection and AddOpenTelemetry() and NOT Add*Elastic*OpenTelemetry()
// we treat this a NOOP
// NOTE for AddElasticOpenTelemetry(this IServiceCollection services) calling Build() manually is NOT required.
if (builder is not AgentBuilder agentBuilder)
return new EmptyAgent();

var log = agentBuilder.Logger;

log.SetAdditionalLogger(logger);

var otelBuilder = agentBuilder.Services.AddOpenTelemetry();
otelBuilder
.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>()!;

var agent = new ElasticAgent(log, agentBuilder.EventListener, tracerProvider, meterProvider);
log.LogAgentBuilderBuiltAgent();
return agent;
}
}

internal class EmptyAgent : IAgent
{
public void Dispose() { }

public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}

internal class ElasticAgent(
AgentCompositeLogger logger,
LoggingEventListener loggingEventListener,
TracerProvider tracerProvider,
MeterProvider meterProvider
) : IAgent
{
public void Dispose()
{
tracerProvider.Dispose();
meterProvider.Dispose();
loggingEventListener.Dispose();
logger.Dispose();
}

public async ValueTask DisposeAsync()
{
tracerProvider.Dispose();
meterProvider.Dispose();
await loggingEventListener.DisposeAsync().ConfigureAwait(false);
await logger.DisposeAsync().ConfigureAwait(false);
}
}

Loading

0 comments on commit f2f6f6c

Please sign in to comment.