Structured Logging
Logging in ETLBox provides insight into task execution, performance, and errors across your data workflows. Built on top of Microsoft’s logging extensions, it supports structured log output, flexible integration with logging frameworks like NLog and Serilog, and detailed context for each task. Whether you're running pipelines in production or debugging locally, ETLBox logging helps you track exactly what happened, when, and why.
Microsoft Logging Extensions
ETLBox integrates with Microsoft.Extensions.Logging
, which allows you to connect it to any compatible logging provider such as NLog
or Serilog
. This makes it easy to capture and route structured log messages from ETLBox tasks and components into log files, monitoring systems, or consoles.
Structured Logging
All ETLBox log output is structured. Each log entry includes metadata that provides context about the component generating the message. These metadata values are available via structured log scopes and include:
taskName
: The name of the component (default is the class name; can be overridden viaTaskName
)taskType
: The class name of the component (e.g.,DbSource
,CsvSource
)taskHash
: A unique hash representing the component instance
If the component is part of a parent task (e.g., a LookupTransformation
within a DbMerge
), ETLBox also includes:
rootTaskName
,rootTaskType
,rootTaskHash
: Metadata from the top-level component
In addition, some log entries contain event-specific information:
action
: Indicates phase such asSTART
orEND
progressCount
: A counter for tracking progress in batch or row-based operations
You can enrich the logging scope with your own custom metadata:
Settings.AdditionalScope = new Dictionary<string, object> {
{ "env", "test" },
{ "runId", "2024-04-01_01" }
};
If you use the optional ETLBox.Logging
package, and manage your ETL using LoadProcess
, the loadProcessId
will also be added to the scope automatically.
Configuring Log Output
To control the level of detail in your logs, use the LogThreshold
property on individual tasks or the global Settings.LogThreshold
. This determines how frequently progress log messages are emitted.
1
: Log every row or batchn
: Log after every n rows or batches
Example:
DbSource source = new DbSource("InputTable") {
LogThreshold = 100
};
This logs progress every 100 records processed.
Disabling Logging
You can selectively disable logging:
- For a specific task or component using
DisableLogging = true
:
DbDestination dest = new DbDestination("TargetTable") {
DisableLogging = true
};
- Globally for all components:
Logging.DisableAllLogging = true;
Logging can be re-enabled at any time by resetting these flags.
Understood. Here’s the refined and streamlined Serilog section for your documentation. This version improves clarity, aligns with .NET logging best practices, and stays focused on ETLBox integration.
Serilog Integration
ETLBox integrates seamlessly with Serilog
through Microsoft.Extensions.Logging
. This allows you to route ETLBox log messages into any Serilog sink (e.g., console, file, Elasticsearch).
To use Serilog, install the following NuGet packages:
Serilog.Extensions.Logging
Serilog.Extensions.Hosting
- Any additional sink you want to use (e.g.,
Serilog.Sinks.Console
,Serilog.Sinks.File
)
Configuration Example (Console Application)
var serilogLogger = new LoggerConfiguration()
.Enrich.FromLogContext()
.MinimumLevel.Debug()
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3} {taskName}] {Message:lj}{NewLine}")
.CreateLogger();
Settings.LogInstance = new SerilogLoggerFactory(serilogLogger).CreateLogger("Default");
This assigns the Serilog logger to ETLBox via Settings.LogInstance
. Log entries will now flow through Serilog’s pipeline.
Structured Output
ETLBox provides structured logging fields that Serilog can access in the output template:
{taskName}
{taskType}
{taskHash}
{rootTaskName}
{rootTaskType}
{rootTaskHash}
{action}
{progressCount}
These values can be used directly in Serilog’s output templates or routed to sinks that support structured logging (e.g., JSON sinks).
Note: Make sure to call
.Enrich.FromLogContext()
in your configuration to enable scope-based property tracking from ETLBox.
NLog Integration
ETLBox supports logging via NLog
using the Microsoft.Extensions.Logging
interface. This lets you route ETLBox log output to files, the console, or any other NLog-supported target.
Refer to the official NLog setup guide for .NET for more context.
Create a nlog.config
file and place it in the root of your project. Ensure its Copy to Output Directory property is set to Copy if newer
or Always
.
Assigning the Logger
Use LoggerFactory
to configure NLog and assign it to ETLBox:
using var loggerFactory = LoggerFactory.Create(builder => {
builder
.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.SetMinimumLevel(LogLevel.Trace)
.AddNLog("nlog.config");
});
Settings.LogInstance = loggerFactory.CreateLogger("Default");
Once assigned to Settings.LogInstance
, ETLBox components will begin emitting structured log messages through NLog.
Sample nlog.config
This configuration logs messages to both the console and a file. It uses NLog’s layout renderers to format structured data from ETLBox.
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogFile="internal-nlog.log"
internalLogLevel="Info">
<targets>
<target name="logfile" xsi:type="File" fileName="etlbox-log.txt"
deleteOldFileOnStartup="true"
layout="${longdate}|${level}|${mdlc:item=taskType}|${event-properties:item=action}|${mdlc:item=taskHash}|${mdlc:item=taskName}|${message}" />
<target name="logconsole" xsi:type="Console"
layout="${longdate}|${level}|${pad:padding=20:inner=${mdlc:item=taskType}}|${pad:padding=5:inner=${event-properties:item=action}}|${pad:padding=20:inner=${mdlc:item=taskName}}|${message}" />
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="logfile,logconsole" />
</rules>
</nlog>
Available Log Properties
The following log properties from ETLBox can be accessed in NLog layouts:
Property | Renderer Example |
---|---|
Task name | ${mdlc:item=taskName} |
Task type | ${mdlc:item=taskType} |
Task hash | ${mdlc:item=taskHash} |
Root task name | ${mdlc:item=rootTaskName} |
Root task type | ${mdlc:item=rootTaskType} |
Root task hash | ${mdlc:item=rootTaskHash} |
Action (START/END) | ${event-properties:item=action} |
Progress count | ${event-properties:item=progressCount} |
Load process ID (if using LoadProcess ) | ${mdlc:item=loadProcessId} |
Debugging NLog Setup
NLog fails silently by default if the configuration is incorrect. To make NLog throw configuration errors and generate internal diagnostics:
<nlog throwConfigExceptions="true"
internalLogFile="nlog-internal.log"
internalLogLevel="Info"
... >
This will help you troubleshoot missing targets, layout errors, or invalid rules.
Logging in Unit Tests
When testing ETLBox components, it’s often useful to verify log output or capture logs for debugging. ETLBox uses Microsoft.Extensions.Logging
, which allows you to plug in mock loggers or custom implementations to suit your test framework.
Using a Custom Logger for xUnit
In xUnit, you can integrate ETLBox logging with the test output by assigning a custom ILogger
implementation to Settings.LogInstance
. The example below shows a simple logger that captures log messages and optionally forwards them to xUnit’s ITestOutputHelper
.
Custom XunitLogger
public class XunitLogger : ILogger
{
private readonly ITestOutputHelper _output;
private readonly StringBuilder _messages = new();
public string AllMessages => _messages.ToString();
public XunitLogger(ITestOutputHelper output) => _output = output;
public XunitLogger() { }
public void Log<TState>(LogLevel logLevel, EventId eventId,
TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var message = formatter(state, exception);
_messages.AppendLine(message);
_output?.WriteLine(message);
}
public bool IsEnabled(LogLevel logLevel) => true;
public IDisposable BeginScope<TState>(TState state) => NullScope.Instance;
private class NullScope : IDisposable
{
public static NullScope Instance { get; } = new();
public void Dispose() { }
}
}
Example Usage in a Test
public class LoggingTests
{
private readonly ITestOutputHelper _output;
public LoggingTests(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void LogOutputIsCaptured()
{
var logger = new XunitLogger(_output);
Settings.LogInstance = logger;
var task = new SqlTask("LogTest", "SELECT 1");
task.ExecuteNonQuery();
Assert.Contains("LogTest", logger.AllMessages);
}
}
Avoiding Moq for High-Volume Logs
While you can mock ILogger
with Moq for simple tests, it’s not recommended for scenarios with high-frequency logging (e.g., row-level log messages). Moq tracks every interaction, which may lead to memory overhead and slow execution.
For performance and stability, use lightweight custom loggers (like the XunitLogger
) in high-volume tests.