# Building a custom feature This sample illustrates how to build a custom [NServiceBus feature](/nservicebus/pipeline/features.md). In this feature, some diagnostics are performed: * Logging the time it takes to process messages in a [message handler](/nservicebus/handlers/index.md) * Logging the state of [sagas](/nservicebus/sagas/index.md) Both of these are implemented as features that depend on the diagnostics feature. ## Diagnostics feature ```cs public class DiagnosticsFeature : Feature { internal DiagnosticsFeature() { EnableByDefault(); } protected override void Setup(FeatureConfigurationContext context) { context.Services.AddSingleton(); } } ``` 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: ```cs public class CustomLogger { static ILog log = LogManager.GetLogger(); 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: ```cs public class HandlerTimerFeature : Feature { internal HandlerTimerFeature() { EnableByDefault(); DependsOn(); } 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](/nservicebus/pipeline/manipulate-with-behaviors.md) that performs the handler timing: ```cs class HandlerTimerBehavior : Behavior { CustomLogger logger; public HandlerTimerBehavior(CustomLogger logger) { this.logger = logger; } public override async Task Invoke(IInvokeHandlerContext context, Func 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](/nservicebus/sagas/index.md) features. ```cs public class SagaStateAuditFeature : Feature { internal SagaStateAuditFeature() { EnableByDefault(); DependsOn(); DependsOn(); } 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](/nservicebus/pipeline/manipulate-with-behaviors.md) that captures the saga state: ```cs class SagaStateAuditBehavior : Behavior { CustomLogger logger; public SagaStateAuditBehavior(CustomLogger logger) { this.logger = logger; } public override async Task Invoke(IInvokeHandlerContext context, Func next) { await next(); if (context.Extensions.TryGet(out ActiveSagaInstance activeSagaInstance)) { var instance = activeSagaInstance.Instance.Entity; logger.WriteSaga(instance); } } } ```