Skip to content

Commit

Permalink
feat: add re-usable application name (#472)
Browse files Browse the repository at this point in the history
  • Loading branch information
stijnmoreels authored Oct 14, 2022
1 parent 1b711e7 commit 4cc0bd7
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 47 deletions.
85 changes: 40 additions & 45 deletions docs/preview/03-Features/telemetry-enrichment.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,16 @@ layout: default
---

# Telemetry Enrichment

We provide a variety of enrichers for Serilog:

- [Telemetry Enrichment](#telemetry-enrichment)
- [Installation](#installation)
- [Application Enricher](#application-enricher)
- [Custom Serilog property names](#custom-serilog-property-names)
- [Correlation Enricher](#correlation-enricher)
- [Custom Serilog property names](#custom-serilog-property-names-1)
- [Kubernetes Enricher](#kubernetes-enricher)
- [Custom Serilog property names](#custom-serilog-property-names-2)
- [Version Enricher](#version-enricher)
- [Custom application version](#custom-application-version)
- [Custom Serilog property names](#custom-serilog-property-names-3)
We provide a variety of enrichers for Serilog

## Installation

This feature requires to install our NuGet package

```shell
PM > Install-Package Arcus.Observability.Telemetry.Serilog.Enrichers
```

## Application Enricher

The `Arcus.Observability.Telemetry.Serilog.Enrichers` library provides a [Serilog enricher](https://github.com/serilog/serilog/wiki/Enrichment)
that adds the application's component name to the log event as a log property with the name `ComponentName` and gives the opportunity to choose the location from where the application 'instance' should be retrieved.

Expand Down Expand Up @@ -63,8 +48,33 @@ logger.Information("Some event");
// Output: Some event {ComponentName: My application component, MachineName: MachineName: MyComputer, PodName: demo-app}
```

### Custom Serilog property names
### Automatic Application Insights `Cloud.RoleName` assignment in `TelemetryClient`
When tracking telemetry in Application Insights via the regular `TelemetryClient`, the application enrichment can be configured to use an `IAppName` registered instance so that both the Serilog enrichment as the Microsoft tracking can benefit from this component name assignment.

First, the `IAppName` should be configured in the application services:
```csharp
using Microsoft.Extensions.DependencyInjection;

public void ConfigureServices(IServiceCollection services)
{
services.AddAppName("My application component");
}
```

Afterwards, when the application enricher is added, you can pass in the `IServiceProvider` so that the application name is assigned via the `IAppName` instance:
```csharp
using Microsoft.Extensions.DependencyInjection;
using Serilog;

IServiceProvider provider = ...
ILogger logger = new LoggerConfiguration()
.Enrich.WithComponentName(provider)
.CreateLogger();
```

This setup will make sure that both for Arcus tracking as well as for Microsoft tracking, the same component name will be used.

### Custom Serilog property names
The application enricher allows you to specify the name of the log property that will be added to the log event during enrichment.
By default this is set to `ComponentName`.

Expand All @@ -82,7 +92,6 @@ logger.Information("Some event");
```

## Correlation Enricher

The `Arcus.ObservabilityTelemetry.Serilog.Enrichers` library provides a [Serilog enricher](https://github.com/serilog/serilog/wiki/Enrichment)
that adds the `CorrelationInfo` information from the current context as log properties with the names `OperationId` and `TransactionId`.

Expand Down Expand Up @@ -127,7 +136,6 @@ logger.Information("This event will be enriched with the correlation information
```

### Custom Serilog property names

The correlation information enricher allows you to specify the names of the log properties that will be added to the log event during enrichment.
This is available on all extension overloads. By default the operation ID is set to `OperationId` and the transaction ID to `TransactionId`.

Expand All @@ -148,7 +156,6 @@ logger.Information("Some event");
```

## Kubernetes Enricher

The `Arcus.Observability.Telemetry.Serilog.Enrichers` library provides a [Kubernetes](https://kubernetes.io/) [Serilog enricher](https://github.com/serilog/serilog/wiki/Enrichment)
that adds several machine information from the environment (variables).

Expand Down Expand Up @@ -220,7 +227,6 @@ spec:
```
### Custom Serilog property names
The Kubernetes enricher allows you to specify the names of the log properties that will be added to the log event during enrichment.
By default the node name is set to `NodeName`, the pod name to `PodName`, and the namespace to `Namespace`.

Expand All @@ -239,7 +245,6 @@ logger.Information("Some event");
```

## Version Enricher

The `Arcus.Observability.Telemetry.Serilog.Enrichers` library provides a [Serilog enricher](https://github.com/serilog/serilog/wiki/Enrichment)
that adds (by default) the current runtime assembly version of the product to the log event as a log property with the name `version`.

Expand All @@ -261,7 +266,6 @@ logger.Information("Some event");
```

### Custom application version

The version enricher allows you to specify an `IAppVersion` instance that retrieves your custom application version, which will be used during enrichment.
By default this is set to the version of the current executing assembly.

Expand All @@ -270,13 +274,10 @@ By default this is set to the version of the current executing assembly.
```csharp
using Microsoft.Extensions.DependencyInjection;
public class Startup
public void ConfigureServices(IServiceCollection services)
{
public void ConfigureServices(IServiceCollection services)
{
// Register the `AssemblyAppVersion` instance to retrieve the application version from the assembly where the passed-along `Startup` type is located.
services.AddAssemblyAppVersion<Startup>();
}
// Register the `AssemblyAppVersion` instance to retrieve the application version from the assembly where the passed-along `Startup` type is located.
services.AddAssemblyAppVersion<Startup>();
}
```

Expand All @@ -301,20 +302,17 @@ Or alternatively, you can choose to register the application version so you can
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

public class Startup
public void ConfigureServivces(IServiceCollection services)
{
public void ConfigureServivces(IServiceCollection services)
// Register the `MyApplicationVersion` instance to the registered services (using empty constructor).
services.AddAppVersion<MyApplicationVersion>();

// Register the `MyApplicationVersion` instance using the service provider.
services.AddAppVersion(serviceProvider =>
{
// Register the `MyApplicationVersion` instance to the registered services (using empty constructor).
services.AddAppVersion<MyApplicationVersion>();

// Register the `MyApplicationVersion` instance using the service provider.
services.AddAppVersion(serviceProvider =>
{
var logger = serviceProvider.GetRequiredService<ILogger<MyApplicationVersion>>();
return new MyApplicationVersion(logger);
});
}
var logger = serviceProvider.GetRequiredService<ILogger<MyApplicationVersion>>();
return new MyApplicationVersion(logger);
});
}
```

Expand All @@ -331,7 +329,6 @@ ILogger logger = new LoggerConfiguration()
```

### Custom Serilog property names

The version enricher allows you to specify the name of the property that will be added to the log event during enrichment.
By default this is set to `version`.

Expand All @@ -344,6 +341,4 @@ ILogger logger = new LoggerConfiguration()

logger.Information("Some event");
// Output: Some event {MyVersion: 1.0.0-preview}
```


```
32 changes: 32 additions & 0 deletions src/Arcus.Observability.Telemetry.Core/DefaultAppName.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using GuardNet;

namespace Arcus.Observability.Telemetry.Core
{
/// <summary>
/// Default <see cref="IAppName"/> implementation that uses a static name to set as application name.
/// </summary>
public class DefaultAppName : IAppName
{
private readonly string _componentName;

/// <summary>
/// Initializes a new instance of the <see cref="DefaultAppName" /> class.
/// </summary>
/// <param name="componentName">The functional name to identity the application.</param>
/// <exception cref="ArgumentException">Thrown when the <paramref name="componentName"/> is blank.</exception>
public DefaultAppName(string componentName)
{
Guard.NotNullOrWhitespace(componentName, nameof(componentName), "Requires a non-blank functional name to identity the application");
_componentName = componentName;
}

/// <summary>
/// Represents a way to retrieve the name of an application during the enrichment.
/// </summary>
public string GetApplicationName()
{
return _componentName;
}
}
}
13 changes: 13 additions & 0 deletions src/Arcus.Observability.Telemetry.Core/IAppName.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Arcus.Observability.Telemetry.Core
{
/// <summary>
/// Represents a way to retrieve the name of an application during the enrichment.
/// </summary>
public interface IAppName
{
/// <summary>
/// Represents a way to retrieve the name of an application during the enrichment.
/// </summary>
string GetApplicationName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,36 @@ public static LoggerConfiguration WithComponentName(
return enrichmentConfiguration.With(new ApplicationEnricher(componentName, propertyName));
}

/// <summary>
/// Adds the <see cref="ApplicationEnricher"/> to the logger enrichment configuration which adds the given application's name,
/// where the name is retrieved from a registered <see cref="IAppName"/> in the <paramref name="serviceProvider"/>.
/// </summary>
/// <param name="enrichmentConfiguration">The configuration to add the enricher.</param>
/// <param name="serviceProvider">The service provider instance that has a registered <see cref="IAppName"/>.</param>
/// <param name="propertyName">The name of the property to enrich the log event with the application's name.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="enrichmentConfiguration"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown when the <paramref name="serviceProvider"/> or <paramref name="propertyName"/> is blank.</exception>
/// <exception cref="InvalidOperationException">Thrown when no <see cref="IAppName"/> is registered in the <paramref name="serviceProvider"/>.</exception>
public static LoggerConfiguration WithComponentName(
this LoggerEnrichmentConfiguration enrichmentConfiguration,
IServiceProvider serviceProvider,
string propertyName = ApplicationEnricher.ComponentName)
{
Guard.NotNull(enrichmentConfiguration, nameof(enrichmentConfiguration), "Requires an enrichment configuration to add the application enricher");
Guard.NotNull(serviceProvider, nameof(serviceProvider), $"Requires a services provider collection to look for registered '{nameof(IAppName)}' implementations");
Guard.NotNullOrWhitespace(propertyName, nameof(propertyName), "Requires a non-blank property name to enrich the log event with the current application's name");

var appName = serviceProvider.GetService<IAppName>();
if (appName is null)
{
throw new InvalidOperationException(
$"Cannot retrieve component name because no {nameof(IAppName)} is registered in the application's services");
}

string componentName = appName.GetApplicationName();
return enrichmentConfiguration.With(new ApplicationEnricher(componentName, propertyName));
}

/// <summary>
/// Adds the <see cref="KubernetesEnricher"/> to the logger enrichment configuration which adds Kubernetes information from the environment.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using Arcus.Observability.Telemetry.Core;
using GuardNet;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.Extensibility;

namespace Arcus.Observability.Telemetry.Serilog.Sinks.ApplicationInsights.Configuration
{
/// <summary>
/// Represents an <see cref="ITelemetryInitializer"/> that configures the application information.
/// </summary>
public class ApplicationTelemetryInitializer : ITelemetryInitializer
{
private readonly IAppName _applicationName;

/// <summary>
/// Initializes a new instance of the <see cref="ApplicationTelemetryInitializer" /> class.
/// </summary>
/// <param name="applicationName">The instance to retrieve the application name.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="applicationName"/> is <c>null</c>.</exception>
public ApplicationTelemetryInitializer(IAppName applicationName)
{
Guard.NotNull(applicationName, nameof(applicationName), $"Requires an application name ({nameof(IAppName)}) implementation to retrieve the application name to initialize in the telemetry");
_applicationName = applicationName;
}

/// <summary>
/// Initializes properties of the specified <see cref="T:Microsoft.ApplicationInsights.Channel.ITelemetry" /> object.
/// </summary>
public void Initialize(ITelemetry telemetry)
{
if (telemetry?.Context?.Cloud != null)
{
string componentName = _applicationName.GetApplicationName();
telemetry.Context.Cloud.RoleName = componentName;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using Arcus.Observability.Telemetry.Core;
using Arcus.Observability.Telemetry.Serilog.Sinks.ApplicationInsights.Configuration;
using GuardNet;
using Microsoft.ApplicationInsights.Extensibility;

// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Extensions on the <see cref="IServiceCollection"/> related to Application Insights.
/// </summary>
// ReSharper disable once InconsistentNaming
public static class IServiceCollectionExtensions
{
/// <summary>
/// Adds an <see cref="IAppName"/> implementation to the application which can be used to retrieve the current application's name.
/// </summary>
/// <param name="services">The collection of registered services to add the <see cref="IAppName"/> implementation to.</param>
/// <param name="componentName">The functional name to identity the application.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="services"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown when the <paramref name="componentName"/> is blank.</exception>
public static IServiceCollection AddAppName(
this IServiceCollection services,
string componentName)
{
Guard.NotNull(services, nameof(services), $"Requires a collection of services to add the '{nameof(IAppName)}' implementation");
Guard.NotNullOrWhitespace(componentName, nameof(componentName), "Requires a non-blank functional name to identity the application");

return AddAppName(services, provider => new DefaultAppName(componentName));
}

/// <summary>
/// Adds an <see cref="IAppName"/> implementation to the application which can be used to retrieve the current application's name.
/// </summary>
/// <param name="services">The collection of registered services to add the <see cref="IAppName"/> implementation to.</param>
/// <param name="implementationFactory">The factory function to create the <see cref="IAppName"/> implementation.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="services"/> or the <paramref name="implementationFactory"/> is <c>null</c>.</exception>
public static IServiceCollection AddAppName(
this IServiceCollection services,
Func<IServiceProvider, IAppName> implementationFactory)
{
Guard.NotNull(services, nameof(services), $"Requires a collection of services to add the '{nameof(IAppName)}' implementation");
Guard.NotNull(implementationFactory, nameof(implementationFactory), $"Requires a factory function to create the '{nameof(IAppName)}' implementation");

return services.AddSingleton(implementationFactory)
.AddSingleton<ITelemetryInitializer, ApplicationTelemetryInitializer>();
}
}
}
Loading

0 comments on commit 4cc0bd7

Please sign in to comment.