Skip to content

Commit

Permalink
Feature - move correlation access information to observability (#43)
Browse files Browse the repository at this point in the history
* Feature - move correlation access information to observability

* pr-sug: update correlation info enricher with accessor directly

* pr-fix: update with correct target framework package references

* temp commit

* pr-sug: update with custom correlation info model

* pr-sug: use custom correlation info to the enricher

* pr-sug: use get/set property io throwing for test correlation info

* pr-sug: more info about custom correlation accessing

* pr-sug: update w/ merging master and docs

* pr-sug: use custom enricher for custom correlation enrichment

* Update docs/features/correlation.md

Co-Authored-By: Tom Kerkhove <[email protected]>

* pr-fix: use netcoreapp2.2 package references

* pr-docs: update with PR comments

* pr-sug: use updated correlation info enricher

* pr-sug: use custom options to configure custom correlation info

* pr-sug: use 'new' member modifier for hiding inherited default accessor

* pr-sug: add xml docs for internal proxy implementation

* pr-sug: use protected correlation info enricher

* pr-fix: use 3.0.0 packages

* pr-sug: use methods to get/set correlation info

* pr-fix: update with correlation get/set as methods

* Update telemetry-enrichment.md

* pr-sug: update with more extensible correlation enrichment

* pr-docs: update with custom correlation info options

* pr-fix: use correct overriding

* pr-docs: remove default instance value

* pr-style: place generic restrictions on diff line

Co-authored-by: Tom Kerkhove <[email protected]>
  • Loading branch information
stijnmoreels and tomkerkhove authored Mar 23, 2020
1 parent 84882f1 commit 77489ae
Show file tree
Hide file tree
Showing 24 changed files with 1,205 additions and 8 deletions.
151 changes: 151 additions & 0 deletions docs/features/correlation.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,155 @@ This feature requires to install our NuGet package
PM > Install-Package Arcus.Observability.Correlation
```

## What We Provide

The `Arcus.Observability.Correlation` library provides a way to get access to correlation information across your application.
What it **DOES NOT** provide is how this correlation information is initially set.

It uses the the Microsoft dependency injection mechanism to register an `ICorrelationInfoAccessor` and `ICorrelationInfoAccessor<>` implementation that is available.

**Example**

```csharp
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Adds operation and transaction correlation to the application,
// using the `DefaultCorrelationInfoAccessor` as `ICorrelationInfoAccessor` that stores the `CorrelationInfo` model internally.
services.AddCorrelation();
}
}
```
## Custom Correlation

We register two interfaces during the registration of the correlation: `ICorrealtionInfoAccessor` and `ICorrelationInfoAccessor<>`.
The reason is because some applications require a custom `CorrelationInfo` model, and with using the generic interface `ICorrelationInfoAccessor<>` we can support this.

**Example**

```csharp
public class OrderCorrelationInfo : CorrelationInfo
{
public string OrderId { get; }
}

public class Startup
{
public void ConfigureService(IServiceCollection services)
{
services.AddCorrelation<OrderCorrelationInfo>();
}
}
```

## Accessing Correlation Throughout the Application

When a part of the application needs access to the correlation information, you can inject one of the two interfaces:

```csharp
public class OrderService
{
public OrderService(ICorrelationInfoAccessor accessor)
{
CorrelationInfo correlationInfo = accessor.CorrelationInfo;
}
}
```

Or, alternatively when using custom correlation:

```csharp
public class OrderService
{
public OrderService(ICorrelationInfoAccessor<OrderCorrelationInfo> accessor)
{
OrderCorrelationInfo correlationInfo = accessor.CorrelationInfo;
}
}
```

## Configuration

The library also provides a way configure some correlation specific options that you can later retrieve during get/set of the correlation information in your application.

```csharp
public void ConfigureServices(IServiceCollection services)
{
services.AddCorrelation(options =>
{
// Configuration on the transaction ID (`X-Transaction-ID`) request/response header.
// ---------------------------------------------------------------------------------
// Whether the transaction ID can be specified in the request, and will be used throughout the request handling.
// The request will return early when the `.AllowInRequest` is set to `false` and the request does contain the header (default: true).
options.Transaction.AllowInRequest = true;

// Whether or not the transaction ID should be generated when there isn't any transaction ID found in the request.
// When the `.GenerateWhenNotSpecified` is set to `false` and the request doesn't contain the header, no value will be available for the transaction ID;
// otherwise a GUID will be generated (default: true).
options.Transaction.GenerateWhenNotSpecified = true;

// Whether to include the transaction ID in the response (default: true).
options.Transaction.IncludeInResponse = true;

// The header to look for in the request, and will be set in the response (default: X-Transaction-ID).
options.Transaction.HeaderName = "X-Transaction-ID";

// The function that will generate the transaction ID, when the `.GenerateWhenNotSpecified` is set to `false` and the request doesn't contain the header.
// (default: new `Guid`).
options.Transaction.GenerateId = () => $"Transaction-{Guid.NewGuid()}";

// Configuration on the operation ID (`RequestId`) response header.
// ----------------------------------------------------------------
// Whether to include the operation ID in the response (default: true).
options.Operation.IncludeInResponse = true;

// The header that will contain the operation ID in the response (default: RequestId).
options.Operation.HeaderName = "RequestId";

// The function that will generate the operation ID header value.
// (default: new `Guid`).
options.Operation.GenerateId = () => $"Operation-{Guid.NewGuid()}";
});
}
```

Later in the application, the options can be retrieved by injecting the `IOptions<CorrelationInfoOptions>` type.

### Custom Configuration

We also provide a way to provide custom configuration options when the application uses a custom correlation model.

For example, with a custom correlation model:

```csharp
public class OrderCorrelationInfo : CorrelationInfo
{
public string OrderId { get; }
}
```

We could introduce an `OrderCorrelationInfoOptions` model:

```csharp
public class OrderCorrelationInfoOptions : CorrelationInfoOptions
{
public bool IncludeOrderId { get; set; }
}
```

This custom options model can then be included when registering the correlation:

```csharp
public class Startup
{
public void ConfigureSerivces(IServiceCollection services)
{
services.AddCorrelation<OrderCorrelationInfo, OrderCorrelationInfoOptions>(options => options.IncludeOrderId = true);
}
}
```

[&larr; back](/)
46 changes: 42 additions & 4 deletions docs/features/telemetry-enrichment.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ We provide a variety of enrichers for Serilog:
This feature requires to install our NuGet package

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

## Application Enricher

The `Arcus.Observability.Telemetry.Serilog` library provides a [Serilog enricher](https://github.com/serilog/serilog/wiki/Enrichment)
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`.

**Example**
Expand All @@ -40,7 +40,7 @@ logger.Information("This event will be enriched with the application component's

## Kubernetes Enricher

The `Arcus.Observability.Telemetry.Serilog` library provides a [Kubernetes](https://kubernetes.io/) [Serilog enricher](https://github.com/serilog/serilog/wiki/Enrichment)
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).

**Example**
Expand Down Expand Up @@ -108,7 +108,7 @@ spec:
## Version Enricher
The `Arcus.Observability.Telemetry.Serilog` library provides a [Serilog enricher](https://github.com/serilog/serilog/wiki/Enrichment)
The `Arcus.Observability.Telemetry.Serilog.Enrichers` library provides a [Serilog enricher](https://github.com/serilog/serilog/wiki/Enrichment)
that adds the current runtime assembly version of the product to the log event as a log property with the name `version`.

**Example**
Expand All @@ -125,4 +125,42 @@ ILogger logger = new LoggerConfiguration()
logger.Information("This event will be enriched with the runtime assembly product version");
```

## 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`.

You can use your own `ICorrelationInfoAccessor` implementation to retrieve this `CorrelationInfo` model,
or use the default `DefaultCorrelationInfoAccessor` implementation that stores this model

**Example**
Name: `OperationId`
Value: `52EE2C00-53EE-476E-9DAB-C1234EB4AD0B`

Name: `TransactionId`
Value: `0477E377-414D-47CD-8756-BCBE3DBE3ACB`

**Usage**

```csharp
ILogger logger = new LoggerConfiguration()
.Enrich.WithCorrelationInfo()
.CreateLogger();
logger.Information("This event will be enriched with the correlation information");
```

Or alternatively, with a custom `ICorrelationInfoAccessor`:

```csharp
ICorrelationInfoAccessor myCustomAccessor = ...
ILogger logger = new LoggerConfiguration()
.Enrich.WithCorrelationInfo(myCustomAccessor)
.CreateLogger();
logger.Information("This event will be enriched with the correlation information");
// Output: This event will be enriched with the correlation information {OperationId: 52EE2C00-53EE-476E-9DAB-C1234EB4AD0B, TransactionId: 0477E377-414D-47CD-8756-BCBE3DBE3ACB}
```

[&larr; back](/)
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="2.2.0" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Guard.Net" Version="1.2.0" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using GuardNet;

namespace Arcus.Observability.Correlation
{
/// <summary>
/// Internal proxy implementation to register an <see cref="ICorrelationInfoAccessor{TCorrelationInfo}"/> as an <see cref="ICorrelationInfoAccessor"/>.
/// </summary>
/// <typeparam name="TCorrelationInfo">The custom <see cref="Correlation.CorrelationInfo"/> model.</typeparam>
internal class CorrelationInfoAccessorProxy<TCorrelationInfo> : ICorrelationInfoAccessor
where TCorrelationInfo : CorrelationInfo
{
private readonly ICorrelationInfoAccessor<TCorrelationInfo> _correlationInfoAccessor;

/// <summary>
/// Initializes a new instance of the <see cref="CorrelationInfoAccessorProxy{TCorrelationInfo}"/> class.
/// </summary>
internal CorrelationInfoAccessorProxy(ICorrelationInfoAccessor<TCorrelationInfo> correlationInfoAccessor)
{
Guard.NotNull(correlationInfoAccessor, nameof(correlationInfoAccessor));

_correlationInfoAccessor = correlationInfoAccessor;
}

/// <summary>
/// Gets the current correlation information initialized in this context.
/// </summary>
public CorrelationInfo GetCorrelationInfo()
{
return _correlationInfoAccessor.GetCorrelationInfo();
}

/// <summary>
/// Sets the current correlation information for this context.
/// </summary>
/// <param name="correlationInfo">The correlation model to set.</param>
public void SetCorrelationInfo(CorrelationInfo correlationInfo)
{
if (correlationInfo is TCorrelationInfo typedCorrelationInfo)
{
_correlationInfoAccessor.SetCorrelationInfo(typedCorrelationInfo);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using GuardNet;

namespace Arcus.Observability.Correlation
{
/// <summary>
/// Correlation options specific for the operation ID.
/// </summary>
public class CorrelationInfoOperationOptions
{
private string _headerName = "RequestId";
private Func<string> _generateId = () => Guid.NewGuid().ToString();

/// <summary>
/// Gets or sets whether to include the operation ID in the response.
/// </summary>
/// <remarks>
/// A common use case is to disable tracing info in edge services, so that such details are not exposed to the outside world.
/// </remarks>
public bool IncludeInResponse { get; set; } = true;

/// <summary>
/// Gets or sets the header that will contain the response operation ID.
/// </summary>
public string HeaderName
{
get => _headerName;
set
{
Guard.NotNullOrWhitespace(value, nameof(value), "Correlation operation header cannot be blank");
_headerName = value;
}
}

/// <summary>
/// Gets or sets the function to generate the operation ID when the <see cref="IncludeInResponse"/> is set to <c>true</c> (default: <c>true</c>).
/// </summary>
public Func<string> GenerateId
{
get => _generateId;
set
{
Guard.NotNull(value, nameof(value), "Correlation function to generate an operation ID cannot be 'null'");
_generateId = value;
}
}
}
}
18 changes: 18 additions & 0 deletions src/Arcus.Observability.Correlation/CorrelationInfoOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Arcus.Observability.Correlation
{
/// <summary>
/// Options for handling correlation id on incoming requests.
/// </summary>
public class CorrelationInfoOptions
{
/// <summary>
/// Gets the correlation options specific for the transaction ID.
/// </summary>
public CorrelationInfoTransactionOptions Transaction { get; } = new CorrelationInfoTransactionOptions();

/// <summary>
/// Gets the correlation options specific for the operation ID.
/// </summary>
public CorrelationInfoOperationOptions Operation { get; } = new CorrelationInfoOperationOptions();
}
}
Loading

0 comments on commit 77489ae

Please sign in to comment.