-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add XO.Console.Cli.Instrumentation with support for OpenTelemetry (#6)
Provide middleware that wraps command execution in an Activity
- Loading branch information
Showing
10 changed files
with
495 additions
and
2 deletions.
There are no files selected for viewing
74 changes: 74 additions & 0 deletions
74
XO.Console.Cli.Instrumentation/CommandAppInstrumentationMiddleware.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
using System.Diagnostics; | ||
using Microsoft.Extensions.Hosting; | ||
using Microsoft.Extensions.Options; | ||
using OpenTelemetry.Trace; | ||
|
||
namespace XO.Console.Cli.Instrumentation; | ||
|
||
/// <summary> | ||
/// An <see cref="ICommandApp"/> middleware that wraps command execution in an <see cref="Activity"/>. | ||
/// </summary> | ||
public sealed class CommandAppInstrumentationMiddleware : ICommandAppMiddleware | ||
{ | ||
/// <summary> | ||
/// The name of the <see cref="ActivitySource"/> for activities started by this middleware. | ||
/// </summary> | ||
public const string ActivitySourceName = ThisAssembly.AssemblyName; | ||
|
||
private static readonly ActivitySource Source | ||
= new(ActivitySourceName, version: ThisAssembly.AssemblyInformationalVersion); | ||
|
||
private readonly string _defaultName; | ||
private readonly CommandAppInstrumentationOptions _options; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of <see cref="CommandAppInstrumentationMiddleware"/>. | ||
/// </summary> | ||
/// <param name="env">The hosting environment.</param> | ||
/// <param name="optionsAccessor">The configuration options.</param> | ||
public CommandAppInstrumentationMiddleware(IHostEnvironment env, IOptions<CommandAppInstrumentationOptions> optionsAccessor) | ||
{ | ||
_defaultName = env.ApplicationName; | ||
_options = optionsAccessor.Value; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public async Task<int> ExecuteAsync(ExecutorDelegate next, CommandContext context, CancellationToken cancellationToken) | ||
{ | ||
int? result = null; | ||
var name = String.Join(' ', from token in context.ParseResult.GetVerbs() select token.Value); | ||
if (String.IsNullOrEmpty(name)) | ||
name = _defaultName; | ||
|
||
var activity = Source.StartActivity(name, _options.ActivityKind); | ||
try | ||
{ | ||
if (activity?.IsAllDataRequested == true) | ||
{ | ||
activity.AddTag(TraceSemanticConventions.AttributeCodeFunction, nameof(AsyncCommand.ExecuteAsync)); | ||
activity.AddTag(TraceSemanticConventions.AttributeCodeNamespace, context.Command.GetType().FullName); | ||
_options.EnrichWithCommandContext?.Invoke(activity, context); | ||
} | ||
|
||
result = await next(context, cancellationToken) | ||
.ConfigureAwait(false); | ||
|
||
activity?.AddTag("command.exit_code", result); | ||
} | ||
catch (Exception ex) | ||
{ | ||
if (activity?.IsAllDataRequested == true) | ||
{ | ||
activity.RecordException(ex); | ||
activity.SetStatus(Status.Error.WithDescription(ex.Message)); | ||
} | ||
throw; | ||
} | ||
finally | ||
{ | ||
activity?.Dispose(); | ||
} | ||
|
||
return result.Value; | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
XO.Console.Cli.Instrumentation/CommandAppInstrumentationOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using System.Diagnostics; | ||
|
||
namespace XO.Console.Cli.Instrumentation; | ||
|
||
/// <summary> | ||
/// Configures OpenTelemetry instrumentation for <see cref="ICommandApp"/>. | ||
/// </summary> | ||
public sealed class CommandAppInstrumentationOptions | ||
{ | ||
/// <summary> | ||
/// The <see cref="System.Diagnostics.ActivityKind"/> to assign to command execution activities. | ||
/// </summary> | ||
/// <remarks> | ||
/// Defaults to <see cref="ActivityKind.Producer"/>. Before changing the activity kind, check whether your | ||
/// environment assigns special meaning to certain kinds. | ||
/// </remarks> | ||
public ActivityKind ActivityKind { get; set; } = ActivityKind.Producer; | ||
|
||
/// <summary> | ||
/// A delegate that enriches the <see cref="Activity"/> with information from the execution context. | ||
/// </summary> | ||
public Action<Activity, CommandContext>? EnrichWithCommandContext { get; set; } | ||
} |
35 changes: 35 additions & 0 deletions
35
XO.Console.Cli.Instrumentation/TracerProviderBuilderExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using Microsoft.Extensions.DependencyInjection; | ||
using XO.Console.Cli; | ||
using XO.Console.Cli.Instrumentation; | ||
|
||
namespace OpenTelemetry.Trace; | ||
|
||
/// <summary> | ||
/// <c>XO.Console.Cli</c> extension methods for <see cref="TracerProviderBuilder"/>. | ||
/// </summary> | ||
public static class TracerProviderBuilderExtensions | ||
{ | ||
/// <summary> | ||
/// Enables <see cref="ICommandApp"/> instrumentation. | ||
/// </summary> | ||
/// <param name="builder">The <see cref="TracerProviderBuilder"/> to configure.</param> | ||
/// <param name="configure">A delegate that configures the <see cref="CommandAppInstrumentationOptions"/>.</param> | ||
/// <returns>The same <see cref="TracerProviderBuilder"/> instance.</returns> | ||
public static TracerProviderBuilder AddCommandAppInstrumentation( | ||
this TracerProviderBuilder builder, | ||
Action<CommandAppInstrumentationOptions>? configure = default) | ||
{ | ||
return builder | ||
.AddSource(CommandAppInstrumentationMiddleware.ActivitySourceName) | ||
.ConfigureServices(services => | ||
{ | ||
var optionsBuilder = services.AddOptions<CommandAppInstrumentationOptions>(); | ||
|
||
if (configure != null) | ||
optionsBuilder.Configure(configure); | ||
|
||
services.AddCommandAppMiddleware<CommandAppInstrumentationMiddleware>(); | ||
}) | ||
; | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
XO.Console.Cli.Instrumentation/XO.Console.Cli.Instrumentation.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net6.0</TargetFramework> | ||
<Description>OpenTelemetry instrumentation for XO.Console.Cli</Description> | ||
<GenerateDocumentationFile>true</GenerateDocumentationFile> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="OpenTelemetry" Version="1.5.0" /> | ||
<PackageReference Include="OpenTelemetry.SemanticConventions" Version="1.0.0-rc9.9" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\XO.Console.Cli.Extensions\XO.Console.Cli.Extensions.csproj" /> | ||
<ProjectReference Include="..\XO.Console.Cli\XO.Console.Cli.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
{ | ||
"version": 1, | ||
"dependencies": { | ||
"net6.0": { | ||
"Microsoft.SourceLink.GitHub": { | ||
"type": "Direct", | ||
"requested": "[1.1.1, )", | ||
"resolved": "1.1.1", | ||
"contentHash": "IaJGnOv/M7UQjRJks7B6p7pbPnOwisYGOIzqCz5ilGFTApZ3ktOR+6zJ12ZRPInulBmdAf1SrGdDG2MU8g6XTw==", | ||
"dependencies": { | ||
"Microsoft.Build.Tasks.Git": "1.1.1", | ||
"Microsoft.SourceLink.Common": "1.1.1" | ||
} | ||
}, | ||
"Nerdbank.GitVersioning": { | ||
"type": "Direct", | ||
"requested": "[3.6.133, )", | ||
"resolved": "3.6.133", | ||
"contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw==" | ||
}, | ||
"OpenTelemetry": { | ||
"type": "Direct", | ||
"requested": "[1.5.0, )", | ||
"resolved": "1.5.0", | ||
"contentHash": "Fx44sVmnPkp/JJQSmUC1iHHWjWQ/lBh6wUIUK6SFeTYRdizn2/K/SaQNNy1dlf0ztpWTB6kfFD+xcjBYgdWPgg==", | ||
"dependencies": { | ||
"Microsoft.Extensions.Logging.Configuration": "3.1.0", | ||
"OpenTelemetry.Api.ProviderBuilderExtensions": "1.5.0" | ||
} | ||
}, | ||
"OpenTelemetry.SemanticConventions": { | ||
"type": "Direct", | ||
"requested": "[1.0.0-rc9.9, )", | ||
"resolved": "1.0.0-rc9.9", | ||
"contentHash": "Ag1czlfURv4vthJtbHLWUMO7raVX8/IKlcZRd4RFE8CsBRniXT+wtRvh0RRULYyoTbchOey41U8NkPTIZJl7zg==" | ||
}, | ||
"Microsoft.Build.Tasks.Git": { | ||
"type": "Transitive", | ||
"resolved": "1.1.1", | ||
"contentHash": "AT3HlgTjsqHnWpBHSNeR0KxbLZD7bztlZVj7I8vgeYG9SYqbeFGh0TM/KVtC6fg53nrWHl3VfZFvb5BiQFcY6Q==" | ||
}, | ||
"Microsoft.Extensions.Configuration": { | ||
"type": "Transitive", | ||
"resolved": "3.1.0", | ||
"contentHash": "Lu41BWNmwhKr6LgyQvcYBOge0pPvmiaK8R5UHXX4//wBhonJyWcT2OK1mqYfEM5G7pTf31fPrpIHOT6sN7EGOA==", | ||
"dependencies": { | ||
"Microsoft.Extensions.Configuration.Abstractions": "3.1.0" | ||
} | ||
}, | ||
"Microsoft.Extensions.Configuration.Abstractions": { | ||
"type": "Transitive", | ||
"resolved": "6.0.0", | ||
"contentHash": "qWzV9o+ZRWq+pGm+1dF+R7qTgTYoXvbyowRoBxQJGfqTpqDun2eteerjRQhq5PQ/14S+lqto3Ft4gYaRyl4rdQ==", | ||
"dependencies": { | ||
"Microsoft.Extensions.Primitives": "6.0.0" | ||
} | ||
}, | ||
"Microsoft.Extensions.Configuration.Binder": { | ||
"type": "Transitive", | ||
"resolved": "3.1.0", | ||
"contentHash": "o9eELDBfNkR7sUtYysFZ1Q7BQ1mYt27DMkups/3vu7xgPyOpMD+iAfrBZFzUXT2iw0fmFb8s1gfNBZS+IgjKdQ==", | ||
"dependencies": { | ||
"Microsoft.Extensions.Configuration": "3.1.0" | ||
} | ||
}, | ||
"Microsoft.Extensions.DependencyInjection": { | ||
"type": "Transitive", | ||
"resolved": "3.1.0", | ||
"contentHash": "KVkv3aF2MQpmGFRh4xRx2CNbc2sjDFk+lH4ySrjWSOS+XoY1Xc+sJphw3N0iYOpoeCCq8976ceVYDH8sdx2qIQ==", | ||
"dependencies": { | ||
"Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.0" | ||
} | ||
}, | ||
"Microsoft.Extensions.DependencyInjection.Abstractions": { | ||
"type": "Transitive", | ||
"resolved": "6.0.0", | ||
"contentHash": "xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==" | ||
}, | ||
"Microsoft.Extensions.FileProviders.Abstractions": { | ||
"type": "Transitive", | ||
"resolved": "6.0.0", | ||
"contentHash": "0pd4/fho0gC12rQswaGQxbU34jOS1TPS8lZPpkFCH68ppQjHNHYle9iRuHeev1LhrJ94YPvzcRd8UmIuFk23Qw==", | ||
"dependencies": { | ||
"Microsoft.Extensions.Primitives": "6.0.0" | ||
} | ||
}, | ||
"Microsoft.Extensions.Hosting.Abstractions": { | ||
"type": "Transitive", | ||
"resolved": "6.0.0", | ||
"contentHash": "GcT5l2CYXL6Sa27KCSh0TixsRfADUgth+ojQSD5EkzisZxmGFh7CwzkcYuGwvmXLjr27uWRNrJ2vuuEjMhU05Q==", | ||
"dependencies": { | ||
"Microsoft.Extensions.Configuration.Abstractions": "6.0.0", | ||
"Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", | ||
"Microsoft.Extensions.FileProviders.Abstractions": "6.0.0" | ||
} | ||
}, | ||
"Microsoft.Extensions.Logging": { | ||
"type": "Transitive", | ||
"resolved": "3.1.0", | ||
"contentHash": "P+8sKQ8L4ooL79sxxqwFPxGGC3aBrUDLB/dZqhs4J0XjTyrkeeyJQ4D4nzJB6OnAhy78HIIgQ/RbD6upOXLynw==", | ||
"dependencies": { | ||
"Microsoft.Extensions.Configuration.Binder": "3.1.0", | ||
"Microsoft.Extensions.DependencyInjection": "3.1.0", | ||
"Microsoft.Extensions.Logging.Abstractions": "3.1.0", | ||
"Microsoft.Extensions.Options": "3.1.0" | ||
} | ||
}, | ||
"Microsoft.Extensions.Logging.Abstractions": { | ||
"type": "Transitive", | ||
"resolved": "6.0.0", | ||
"contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" | ||
}, | ||
"Microsoft.Extensions.Logging.Configuration": { | ||
"type": "Transitive", | ||
"resolved": "3.1.0", | ||
"contentHash": "yW3nIoNM3T5iZg8bRViiCN4+vIU/02l+mlWSvKqWnr0Fd5Uk1zKdT9jBWKEcJhRIWKVWWSpFWXnM5yWoIAy1Eg==", | ||
"dependencies": { | ||
"Microsoft.Extensions.Logging": "3.1.0", | ||
"Microsoft.Extensions.Options.ConfigurationExtensions": "3.1.0" | ||
} | ||
}, | ||
"Microsoft.Extensions.Options": { | ||
"type": "Transitive", | ||
"resolved": "6.0.0", | ||
"contentHash": "dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", | ||
"dependencies": { | ||
"Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", | ||
"Microsoft.Extensions.Primitives": "6.0.0" | ||
} | ||
}, | ||
"Microsoft.Extensions.Options.ConfigurationExtensions": { | ||
"type": "Transitive", | ||
"resolved": "3.1.0", | ||
"contentHash": "tx6gMKE3rDspA1YZT8SlQJmyt1BaBSl6mNjB3g0ZO6m3NnoavCifXkGeBuDk9Ae4XjW8C+dty52p+0u38jPRIQ==", | ||
"dependencies": { | ||
"Microsoft.Extensions.Configuration.Abstractions": "3.1.0", | ||
"Microsoft.Extensions.Configuration.Binder": "3.1.0", | ||
"Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.0", | ||
"Microsoft.Extensions.Options": "3.1.0" | ||
} | ||
}, | ||
"Microsoft.Extensions.Primitives": { | ||
"type": "Transitive", | ||
"resolved": "6.0.0", | ||
"contentHash": "9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==", | ||
"dependencies": { | ||
"System.Runtime.CompilerServices.Unsafe": "6.0.0" | ||
} | ||
}, | ||
"Microsoft.SourceLink.Common": { | ||
"type": "Transitive", | ||
"resolved": "1.1.1", | ||
"contentHash": "WMcGpWKrmJmzrNeuaEb23bEMnbtR/vLmvZtkAP5qWu7vQsY59GqfRJd65sFpBszbd2k/bQ8cs8eWawQKAabkVg==" | ||
}, | ||
"OpenTelemetry.Api": { | ||
"type": "Transitive", | ||
"resolved": "1.5.0", | ||
"contentHash": "aAugEK9E+ono8I2Crjel78mrpEreJtcK1uzCYVooYELnSEPYytrzJYvw5SBxNizXT/qOBbz+EsfO+rkQfW7Mkg==", | ||
"dependencies": { | ||
"System.Diagnostics.DiagnosticSource": "7.0.0" | ||
} | ||
}, | ||
"OpenTelemetry.Api.ProviderBuilderExtensions": { | ||
"type": "Transitive", | ||
"resolved": "1.5.0", | ||
"contentHash": "Wv28j71V1mizHjBfNx/ILhgqkXpbBZbCcZRoQMvqkF+Pp1bHHJpxOuipDXLqpO7KKHTwrTVl0/TAUHzoXXRK0g==", | ||
"dependencies": { | ||
"Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.0", | ||
"OpenTelemetry.Api": "1.5.0" | ||
} | ||
}, | ||
"System.Diagnostics.DiagnosticSource": { | ||
"type": "Transitive", | ||
"resolved": "7.0.0", | ||
"contentHash": "9W0ewWDuAyDqS2PigdTxk6jDKonfgscY/hP8hm7VpxYhNHZHKvZTdRckberlFk3VnCmr3xBUyMBut12Q+T2aOw==", | ||
"dependencies": { | ||
"System.Runtime.CompilerServices.Unsafe": "6.0.0" | ||
} | ||
}, | ||
"System.Runtime.CompilerServices.Unsafe": { | ||
"type": "Transitive", | ||
"resolved": "6.0.0", | ||
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" | ||
}, | ||
"xo.console.cli": { | ||
"type": "Project" | ||
}, | ||
"xo.console.cli.extensions": { | ||
"type": "Project", | ||
"dependencies": { | ||
"Microsoft.Extensions.Hosting.Abstractions": "[6.0.0, )", | ||
"Microsoft.Extensions.Logging.Abstractions": "[6.0.0, )", | ||
"Microsoft.Extensions.Options": "[6.0.0, )", | ||
"XO.Console.Cli": "[1.0.0, )" | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", | ||
"inherit": true, | ||
"pathFilters": [ | ||
"../Directory.Build.props", | ||
"../global.json", | ||
"." | ||
] | ||
} |
Oops, something went wrong.