Custom Global Error Handler
Albatross.CommandLine provides the ICommandErrorHandler interface to plug in custom exception handling logic that runs when an unhandled exception escapes a command handler.
How It Works
When a command handler throws an unhandled exception, the GlobalCommandAction catches it and checks whether an ICommandErrorHandler is registered in the DI container:
- If a handler is registered and its
Handlemethod returns a non-null exit code, that exit code is returned and the default error logging is suppressed. - If no handler is registered, or
Handlereturns null, the default behavior applies: the exception is logged at theErrorlevel and exit code255is returned.
flowchart TD
Exception([Unhandled Exception]) --> Lookup[Resolve ICommandErrorHandler<br/>from DI]
Lookup --> Registered(Handler Registered?)
Registered -->|No| DefaultLog[Log Error<br/>Return 255]
Registered -->|Yes| CallHandle[Call Handle]
CallHandle --> NonNull(Returns non-null?)
NonNull -->|Yes| ReturnCode[Return Custom Exit Code<br/>No logging]
NonNull -->|No| DefaultLog
DefaultLog --> Exit([Exit])
ReturnCode --> Exit
Interface Definition
public interface ICommandErrorHandler {
/// <summary>
/// Called when an unhandled exception escapes a command handler.
/// Return a non-null exit code to suppress default error logging.
/// Return null to fall through to default behavior.
/// </summary>
int? Handle(Exception exception);
}
Implementing a Custom Handler
public class MyErrorHandler : ICommandErrorHandler {
private readonly ILogger<MyErrorHandler> logger;
public MyErrorHandler(ILogger<MyErrorHandler> logger) {
this.logger = logger;
}
public int? Handle(Exception exception) {
if (exception is ValidationException validationEx) {
logger.LogWarning("Validation failed: {message}", validationEx.Message);
return 2;
}
if (exception is UnauthorizedAccessException) {
logger.LogWarning("Access denied");
return 3;
}
// Return null to fall through to default error logging
return null;
}
}
Registration
Register the handler in your DI setup:
void RegisterServices(HostBuilderContext ctx, IServiceCollection services) {
services.AddSingleton<ICommandErrorHandler, MyErrorHandler>();
}
await using var host = new CommandHost("My App")
.RegisterServices(RegisterServices)
.AddCommands()
.Parse(args)
.Build();
return await host.InvokeAsync();
Note
Only one ICommandErrorHandler can be registered. If you need to handle multiple exception types, dispatch within a single implementation.
Exit Code Conventions
The following exit codes are reserved by GlobalCommandAction for its default behaviors:
| Exit Code | Meaning |
|---|---|
| 255 | Unhandled exception (default) |
| 254 | Command cancelled (OperationCanceledException) |
| 253 | Input action (option handler) error |
Avoid returning these values from your custom handler to prevent ambiguity.