Listeners

Listeners let you hook into the job and step lifecycle for cross-cutting concerns like logging, metrics, notifications, or auditing.


Job Listeners

Implement IJobListener to receive callbacks before and after a job executes:

public interface IJobListener
{
    Task BeforeJobAsync(string jobName, CancellationToken cancellationToken);
    Task AfterJobAsync(JobResult result, CancellationToken cancellationToken);
}

Both methods have no-op defaults – implement only what you need.

Example

public class TimingListener : IJobListener
{
    private Stopwatch _sw = default!;

    public Task BeforeJobAsync(string jobName, CancellationToken ct)
    {
        _sw = Stopwatch.StartNew();
        Console.WriteLine($"Job '{jobName}' starting...");
        return Task.CompletedTask;
    }

    public Task AfterJobAsync(JobResult result, CancellationToken ct)
    {
        _sw.Stop();
        Console.WriteLine($"Job '{result.Name}' finished in {_sw.Elapsed} -- Success: {result.Success}");
        return Task.CompletedTask;
    }
}

Registration

var job = Job.CreateBuilder("my-job")
    .WithListener(new TimingListener())
    .AddStep("work", step => step
        .ReadFrom(reader)
        .WriteTo(writer))
    .Build();

Step Listeners

Implement IStepListener to receive callbacks before and after each step:

public interface IStepListener
{
    Task BeforeStepAsync(string stepName, CancellationToken cancellationToken);
    Task AfterStepAsync(StepResult result, CancellationToken cancellationToken);
}

Example

public class StepMetricsListener : IStepListener
{
    public Task AfterStepAsync(StepResult result, CancellationToken ct)
    {
        Console.WriteLine(
            $"Step '{result.Name}': Read={result.ItemsRead}, " +
            $"Processed={result.ItemsProcessed}, Skipped={result.ErrorsSkipped}");
        return Task.CompletedTask;
    }
}

Registration

Step listeners are registered per step:

var job = Job.CreateBuilder("my-job")
    .AddStep("import", step => step
        .ReadFrom(reader)
        .WriteTo(writer)
        .WithListener(new StepMetricsListener()))
    .Build();

Combining Listeners

You can register multiple listeners at both levels:

var job = Job.CreateBuilder("monitored-job")
    .WithListener(new TimingListener())          // job-level
    .WithListener(new SlackNotifyListener())     // job-level
    .AddStep("extract", step => step
        .ReadFrom(reader)
        .WriteTo(writer)
        .WithListener(new StepMetricsListener())   // step-level
        .WithListener(new StepLoggingListener()))   // step-level
    .AddStep("notify", step => step
        .Execute(() => SendEmailAsync()))
    .Build();

Common Use Cases

Use Case Listener Type Implementation
Job timing IJobListener Start/stop a Stopwatch
Slack/email alerts IJobListener Send notification in AfterJobAsync on failure
Per-step metrics IStepListener Log ItemsRead, ItemsProcessed, ErrorsSkipped
Audit logging Both Write entries to an audit trail
Health checks IJobListener Update a health-check endpoint

Next: API Reference →


Back to top

NBatch — lightweight batch processing for .NET.