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

(❗) chore: rework correlation info options #462

114 changes: 1 addition & 113 deletions docs/preview/03-Features/correlation.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,116 +106,4 @@ namespace Application
}
}
}
```

## 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()}";

// Configuration on the operation parent ID.
// -----------------------------------------

// Whether to extract the operation parent ID from the request or not (default: true).
options.OperationParent.ExtractFromRequest = false;

// The header that will contain the full operation parent ID (default: Request-Id).
options.OperationParent.OperationParentHeaderName = "Request-Id";

// The function that will generate the operation parent ID when it shouldn't be extracted from the request.
options.OperationParent.GenerateId = () => $"Parent-{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
using Arcus.Observability.Correlation;

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

We could introduce an `OrderCorrelationInfoOptions` model:

```csharp
using Arcus.Observability.Correlation;

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

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

```csharp
using Microsoft.Extensions.DependencyInjection;

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


```
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Arcus.Observability.Correlation
/// <summary>
/// Correlation options specific for the operation ID.
/// </summary>
[Obsolete("Use HTTP, messaging, or custom-specific correlation options instead")]
public class CorrelationInfoOperationOptions
{
private string _headerName = "RequestId";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
namespace Arcus.Observability.Correlation
using System;

namespace Arcus.Observability.Correlation
{
/// <summary>
/// Options for handling correlation id on incoming requests.
/// </summary>
[Obsolete("Use HTTP, messaging, or custom-specific correlation options instead")]
public class CorrelationInfoOptions
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Arcus.Observability.Correlation
/// <summary>
/// Correlation options specific to the transaction ID.
/// </summary>
[Obsolete("Use HTTP, messaging, or custom-specific correlation options instead")]
public class CorrelationInfoTransactionOptions
{
private string _headerName = "X-Transaction-ID";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Arcus.Observability.Correlation
/// <summary>
/// Correlation options specific to the upstream services, used in the <see cref="CorrelationInfoOptions"/>.
/// </summary>
[Obsolete("Use HTTP, messaging, or custom-specific correlation options instead")]
public class CorrelationInfoUpstreamServiceOptions
{
private string _operationParentIdHeaderName = "Request-Id";
Expand Down Expand Up @@ -44,4 +45,4 @@ public Func<string> GenerateId
}
}
}
}
}
100 changes: 89 additions & 11 deletions src/Arcus.Observability.Correlation/IServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,60 @@ namespace Microsoft.Extensions.DependencyInjection
// ReSharper disable once InconsistentNaming
public static class IServiceCollectionExtensions
{
/// <summary>
/// Adds operation and transaction correlation to the application using the <see cref="DefaultCorrelationInfoAccessor"/>
/// </summary>
/// <param name="services">The services collection containing the dependency injection services.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="services"/> is <c>null</c>.</exception>
public static IServiceCollection AddCorrelation(this IServiceCollection services)
{
Guard.NotNull(services, nameof(services), "Requires a service collection to register the default correlation accessor to the application services");

return AddCorrelation<CorrelationInfo>(services);
}

/// <summary>
/// Adds operation and transaction correlation to the application using the <see cref="DefaultCorrelationInfoAccessor"/>
/// </summary>
/// <param name="services">The services collection containing the dependency injection services.</param>
/// <param name="configureOptions">The function to configure additional options how the correlation works.</param>
[Obsolete("Use HTTP, messaging, or custom-specific correlation registration instead")]
public static IServiceCollection AddCorrelation(
this IServiceCollection services,
Action<CorrelationInfoOptions> configureOptions = null)
Action<CorrelationInfoOptions> configureOptions)
{
Guard.NotNull(services, nameof(services));

return AddCorrelation(services, new DefaultCorrelationInfoAccessor(), configureOptions);
}

/// <summary>
/// Adds operation and transaction correlation to the application using the <see cref="DefaultCorrelationInfoAccessor{TCorrelationInfo}"/>
/// </summary>
/// <typeparam name="TCorrelationInfo">The type of the <see cref="CorrelationInfo"/> model.</typeparam>
/// <param name="services">The services collection containing the dependency injection services.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="services"/> is <c>null</c>.</exception>
public static IServiceCollection AddCorrelation<TCorrelationInfo>(
fgheysels marked this conversation as resolved.
Show resolved Hide resolved
this IServiceCollection services)
where TCorrelationInfo : CorrelationInfo
{
Guard.NotNull(services, nameof(services), "Requires a service collection to register the default correlation accessor to the application services");

return AddCorrelation<DefaultCorrelationInfoAccessor<TCorrelationInfo>, TCorrelationInfo>(
services,
provider => new DefaultCorrelationInfoAccessor<TCorrelationInfo>());
}

/// <summary>
/// Adds operation and transaction correlation to the application using the <see cref="DefaultCorrelationInfoAccessor{TCorrelationInfo}"/>
/// </summary>
/// <typeparam name="TCorrelationInfo">The type of the <see cref="CorrelationInfo"/> model.</typeparam>
/// <param name="services">The services collection containing the dependency injection services.</param>
/// <param name="configureOptions">The function to configure additional options how the correlation works.</param>
[Obsolete("Use HTTP, messaging, or custom-specific correlation registration instead")]
public static IServiceCollection AddCorrelation<TCorrelationInfo>(
this IServiceCollection services,
Action<CorrelationInfoOptions> configureOptions = null)
Action<CorrelationInfoOptions> configureOptions)
where TCorrelationInfo : CorrelationInfo
{
Guard.NotNull(services, nameof(services));
Expand All @@ -48,6 +79,7 @@ public static IServiceCollection AddCorrelation<TCorrelationInfo>(
/// <typeparam name="TOptions">The type of the custom <see cref="CorrelationInfoOptions"/> model.</typeparam>
/// <param name="services">The services collection containing the dependency injection services.</param>
/// <param name="configureOptions">The function to configure additional options how the correlation works.</param>
[Obsolete("Use HTTP, messaging, or custom-specific correlation registration instead")]
public static IServiceCollection AddCorrelation<TCorrelationInfo, TOptions>(
this IServiceCollection services,
Action<TOptions> configureOptions = null)
Expand All @@ -69,6 +101,7 @@ public static IServiceCollection AddCorrelation<TCorrelationInfo, TOptions>(
/// <param name="services">The services collection containing the dependency injection services.</param>
/// <param name="customCorrelationAccessor">The custom <see cref="ICorrelationInfoAccessor"/> implementation to retrieve the <see cref="CorrelationInfo"/>.</param>
/// <param name="configureOptions">The function to configure additional options how the correlation works.</param>
[Obsolete("Use HTTP, messaging, or custom-specific correlation registration instead")]
public static IServiceCollection AddCorrelation<TAccessor>(
this IServiceCollection services,
TAccessor customCorrelationAccessor,
Expand All @@ -81,17 +114,36 @@ public static IServiceCollection AddCorrelation<TAccessor>(
return AddCorrelation(services, serviceProvider => customCorrelationAccessor, configureOptions);
}

/// <summary>
/// Adds operation and transaction correlation to the application.
/// </summary>
/// <typeparam name="TAccessor">The type of the <see cref="ICorrelationInfoAccessor"/> implementation.</typeparam>
/// <param name="services">The services collection containing the dependency injection services.</param>
/// <param name="createCustomCorrelationAccessor">The custom <see cref="ICorrelationInfoAccessor"/> implementation factory to retrieve the <see cref="CorrelationInfo"/>.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="services"/> or the <paramref name="createCustomCorrelationAccessor"/> is <c>null</c>.</exception>
public static IServiceCollection AddCorrelation<TAccessor>(
this IServiceCollection services,
Func<IServiceProvider, TAccessor> createCustomCorrelationAccessor)
where TAccessor : class, ICorrelationInfoAccessor
{
Guard.NotNull(services, nameof(services), "Requires a service collection to register the custom correlation accessor to the application services");
Guard.NotNull(createCustomCorrelationAccessor, nameof(createCustomCorrelationAccessor), "Requires a factory function to create a custom correlation accessor");

return AddCorrelation<TAccessor, CorrelationInfo>(services, createCustomCorrelationAccessor);
}

/// <summary>
/// Adds operation and transaction correlation to the application.
/// </summary>
/// <typeparam name="TAccessor">The type of the <see cref="ICorrelationInfoAccessor"/> implementation.</typeparam>
/// <param name="services">The services collection containing the dependency injection services.</param>
/// <param name="createCustomCorrelationAccessor">The custom <see cref="ICorrelationInfoAccessor"/> implementation factory to retrieve the <see cref="CorrelationInfo"/>.</param>
/// <param name="configureOptions">The function to configure additional options how the correlation works.</param>
[Obsolete("Use HTTP, messaging, or custom-specific correlation registration instead")]
public static IServiceCollection AddCorrelation<TAccessor>(
this IServiceCollection services,
Func<IServiceProvider, TAccessor> createCustomCorrelationAccessor,
Action<CorrelationInfoOptions> configureOptions = null)
Action<CorrelationInfoOptions> configureOptions)
where TAccessor : class, ICorrelationInfoAccessor
{
Guard.NotNull(services, nameof(services));
Expand All @@ -100,6 +152,33 @@ public static IServiceCollection AddCorrelation<TAccessor>(
return AddCorrelation<TAccessor, CorrelationInfo>(services, createCustomCorrelationAccessor, configureOptions);
}

/// <summary>
/// Adds operation and transaction correlation to the application.
/// </summary>
/// <typeparam name="TAccessor">The type of the <see cref="ICorrelationInfoAccessor"/> implementation.</typeparam>
/// <typeparam name="TCorrelationInfo">The type of the custom <see cref="CorrelationInfo"/> model.</typeparam>
/// <param name="services">The services collection containing the dependency injection services.</param>
/// <param name="createCustomCorrelationAccessor">The custom <see cref="ICorrelationInfoAccessor"/> implementation factory to retrieve the <see cref="CorrelationInfo"/>.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="services"/> or the <paramref name="createCustomCorrelationAccessor"/> is <c>null</c>.</exception>
public static IServiceCollection AddCorrelation<TAccessor, TCorrelationInfo>(
this IServiceCollection services,
Func<IServiceProvider, TAccessor> createCustomCorrelationAccessor)
where TAccessor : class, ICorrelationInfoAccessor<TCorrelationInfo>
where TCorrelationInfo : CorrelationInfo
{
Guard.NotNull(services, nameof(services), "Requires a service collection to register the custom correlation accessor to the application services");
Guard.NotNull(createCustomCorrelationAccessor, nameof(createCustomCorrelationAccessor), "Requires a factory function to create a custom correlation accessor");

services.AddScoped<ICorrelationInfoAccessor<TCorrelationInfo>>(createCustomCorrelationAccessor);
services.AddScoped<ICorrelationInfoAccessor>(serviceProvider =>
{
return new CorrelationInfoAccessorProxy<TCorrelationInfo>(
serviceProvider.GetRequiredService<ICorrelationInfoAccessor<TCorrelationInfo>>());
});

return services;
}

/// <summary>
/// Adds operation and transaction correlation to the application.
/// </summary>
Expand All @@ -108,10 +187,11 @@ public static IServiceCollection AddCorrelation<TAccessor>(
/// <param name="services">The services collection containing the dependency injection services.</param>
/// <param name="createCustomCorrelationAccessor">The custom <see cref="ICorrelationInfoAccessor"/> implementation factory to retrieve the <see cref="CorrelationInfo"/>.</param>
/// <param name="configureOptions">The function to configure additional options how the correlation works.</param>
[Obsolete("Use HTTP, messaging, or custom-specific correlation registration instead")]
public static IServiceCollection AddCorrelation<TAccessor, TCorrelationInfo>(
this IServiceCollection services,
Func<IServiceProvider, TAccessor> createCustomCorrelationAccessor,
Action<CorrelationInfoOptions> configureOptions = null)
Action<CorrelationInfoOptions> configureOptions)
where TAccessor : class, ICorrelationInfoAccessor<TCorrelationInfo>
where TCorrelationInfo : CorrelationInfo
{
Expand All @@ -130,12 +210,13 @@ public static IServiceCollection AddCorrelation<TAccessor, TCorrelationInfo>(
/// <param name="services">The services collection containing the dependency injection services.</param>
/// <param name="createCustomCorrelationAccessor">The custom <see cref="ICorrelationInfoAccessor"/> implementation factory to retrieve the <see cref="CorrelationInfo"/>.</param>
/// <param name="configureOptions">The function to configure additional options how the correlation works.</param>
[Obsolete("Use HTTP, messaging, or custom-specific correlation registration instead")]
public static IServiceCollection AddCorrelation<TAccessor, TCorrelationInfo, TOptions>(
this IServiceCollection services,
Func<IServiceProvider, TAccessor> createCustomCorrelationAccessor,
Action<TOptions> configureOptions = null)
Action<TOptions> configureOptions)
where TAccessor : class, ICorrelationInfoAccessor<TCorrelationInfo>
where TCorrelationInfo : CorrelationInfo
where TCorrelationInfo : CorrelationInfo
where TOptions : CorrelationInfoOptions
{
Guard.NotNull(services, nameof(services));
Expand All @@ -148,12 +229,9 @@ public static IServiceCollection AddCorrelation<TAccessor, TCorrelationInfo, TOp
serviceProvider.GetRequiredService<ICorrelationInfoAccessor<TCorrelationInfo>>());
});

if (configureOptions != null)
{
services.Configure(configureOptions);
}
services.Configure<TOptions>(options => configureOptions?.Invoke(options));

return services;
}
}
}
}