This sample illustrates how to build a custom NServiceBus feature. In this feature, some diagnostics are performed:
- Logging the time it takes to process messages in a message handler
- Logging the state of sagas
Both of these are implemented as features that depend on the diagnostics feature.
Diagnostics feature
public class DiagnosticsFeature :
Feature
{
internal DiagnosticsFeature()
{
EnableByDefault();
}
protected override void Setup(FeatureConfigurationContext context)
{
context.Services.AddSingleton<CustomLogger>();
}
}
The diagnostics feature allows all dependencies to be easily toggled, enabling or disabling them through configuration. In this case, it is enabled by default.
Custom logger
The feature in this sample injects a custom logger that can be used by other features:
public class CustomLogger
{
static ILog log = LogManager.GetLogger<CustomLogger>();
static readonly JsonSerializerOptions options = new() { WriteIndented = true };
public IDisposable StartTimer(string name)
{
return new Log(name);
}
public void WriteSaga(IContainSagaData sagaData)
{
var serialized = JsonSerializer.Serialize(sagaData, options);
log.Warn($"Saga State: \r\n{serialized}");
}
class Log :
IDisposable
{
string name;
Stopwatch stopwatch;
public Log(string name)
{
stopwatch = Stopwatch.StartNew();
this.name = name;
}
public void Dispose()
{
log.Warn($"{name} took {stopwatch.ElapsedMilliseconds}ms to process");
}
}
}
Handler timing feature
The feature depends on the diagnostics feature:
public class HandlerTimerFeature :
Feature
{
internal HandlerTimerFeature()
{
EnableByDefault();
DependsOn<DiagnosticsFeature>();
}
protected override void Setup(FeatureConfigurationContext context)
{
var pipeline = context.Pipeline;
pipeline.Register(
stepId: "HandlerTimer",
behavior: typeof(HandlerTimerBehavior),
description: "Logs handler time");
}
}
Behavior
The pipeline behavior that performs the handler timing:
class HandlerTimerBehavior :
Behavior<IInvokeHandlerContext>
{
CustomLogger logger;
public HandlerTimerBehavior(CustomLogger logger)
{
this.logger = logger;
}
public override async Task Invoke(IInvokeHandlerContext context, Func<Task> next)
{
var handlerName = context.MessageHandler.Instance.GetType().Name;
using (logger.StartTimer(handlerName))
{
await next();
}
}
}
Saga state audit feature
This feature depends on both the Diagnostics and Saga features.
public class SagaStateAuditFeature :
Feature
{
internal SagaStateAuditFeature()
{
EnableByDefault();
DependsOn<Sagas>();
DependsOn<DiagnosticsFeature>();
}
protected override void Setup(FeatureConfigurationContext context)
{
var pipeline = context.Pipeline;
pipeline.Register(
stepId: "SagaStateAudit",
behavior: typeof(SagaStateAuditBehavior),
description: "Logs Saga State");
}
}
Behavior
The pipeline behavior that captures the saga state:
class SagaStateAuditBehavior :
Behavior<IInvokeHandlerContext>
{
CustomLogger logger;
public SagaStateAuditBehavior(CustomLogger logger)
{
this.logger = logger;
}
public override async Task Invoke(IInvokeHandlerContext context, Func<Task> next)
{
await next();
if (context.Extensions.TryGet(out ActiveSagaInstance activeSagaInstance))
{
var instance = activeSagaInstance.Instance.Entity;
logger.WriteSaga(instance);
}
}
}