Skip to content
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

Move our AgentBuilder to IOpenTelemetryBuilder #47

Merged
merged 2 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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