This sample illustrates how to build a custom NServiceBus feature. In this feature, some diagnostics are performed:
Both of these are implemented as dependent features that depend on the diagnostics feature.
Diagnostics feature
public class DiagnosticsFeature :
Feature
{
internal DiagnosticsFeature()
{
EnableByDefault();
}
protected override void Setup(FeatureConfigurationContext context)
{
var container = context.Container;
container.ConfigureComponent<CustomLogger>(DependencyLifecycle.SingleInstance);
}
}
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
This feature injects a custom logger that can be used by other features.
public class CustomLogger
{
static ILog log = LogManager.GetLogger<CustomLogger>();
public IDisposable StartTimer(string name)
{
return new Log(name);
}
public void WriteSaga(IContainSagaData sagaData)
{
var serialized = JsonConvert.SerializeObject(sagaData, Formatting.Indented);
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
This 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()
.ConfigureAwait(false);
}
}
}
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()
.ConfigureAwait(false);
if (context.Extensions.TryGet(out ActiveSagaInstance activeSagaInstance))
{
var instance = activeSagaInstance.Instance.Entity;
logger.WriteSaga(instance);
}
}
}