DEV Community

IronSoftware
IronSoftware

Posted on

Custom Logging in IronPDF for C#

Our operations team needed visibility into PDF generation failures. The default IronPDF console logging wasn't enough—they wanted logs in Application Insights with structured data, alerting, and correlation IDs for tracking requests.

IronPDF supports custom logging through the ILogger interface. Here's how I integrated it with our existing logging infrastructure.

What Is Custom Logging in IronPDF?

Custom logging redirects IronPDF's internal log messages to your own logger implementation. Instead of writing to the console or debug output, IronPDF sends messages to your logger class.

using IronPdf;
using Microsoft.Extensions.Logging;
// Install via NuGet: Install-Package IronPdf

IronSoftware.Logger.LoggingMode = IronSoftware.Logger.LoggingModes.Custom;
IronSoftware.Logger.CustomLogger = new MyCustomLogger();

var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Test</h1>");
// All IronPDF log messages now route to MyCustomLogger
Enter fullscreen mode Exit fullscreen mode

This gives you full control over where logs go and how they're formatted.

How Do I Create a Custom Logger?

Implement the ILogger interface from Microsoft.Extensions.Logging:

using Microsoft.Extensions.Logging;
using System;

public class MyCustomLogger : ILogger
{
    public IDisposable BeginScope<TState>(TState state) => null;

    public bool IsEnabled(LogLevel logLevel) => true;

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
        Exception exception, Func<TState, Exception, string> formatter)
    {
        if (!IsEnabled(logLevel)) return;

        var message = formatter(state, exception);
        Console.WriteLine($"[{logLevel}] IronPDF: {message}");

        if (exception != null)
        {
            Console.WriteLine($"Exception: {exception}");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The Log method receives all log messages from IronPDF. You control what happens with them.

How Do I Integrate with Serilog?

Wrap Serilog's logger:

using Microsoft.Extensions.Logging;
using Serilog;

public class SerilogAdapter : ILogger
{
    private readonly Serilog.ILogger _logger;

    public SerilogAdapter(Serilog.ILogger logger)
    {
        _logger = logger;
    }

    public IDisposable BeginScope<TState>(TState state) => null;

    public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(ConvertLogLevel(logLevel));

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
        Exception exception, Func<TState, Exception, string> formatter)
    {
        if (!IsEnabled(logLevel)) return;

        var message = formatter(state, exception);

        switch (logLevel)
        {
            case LogLevel.Trace:
            case LogLevel.Debug:
                _logger.Debug(message);
                break;
            case LogLevel.Information:
                _logger.Information(message);
                break;
            case LogLevel.Warning:
                _logger.Warning(message);
                break;
            case LogLevel.Error:
            case LogLevel.Critical:
                _logger.Error(exception, message);
                break;
        }
    }

    private Serilog.Events.LogEventLevel ConvertLogLevel(LogLevel logLevel)
    {
        return logLevel switch
        {
            LogLevel.Trace => Serilog.Events.LogEventLevel.Verbose,
            LogLevel.Debug => Serilog.Events.LogEventLevel.Debug,
            LogLevel.Information => Serilog.Events.LogEventLevel.Information,
            LogLevel.Warning => Serilog.Events.LogEventLevel.Warning,
            LogLevel.Error => Serilog.Events.LogEventLevel.Error,
            LogLevel.Critical => Serilog.Events.LogEventLevel.Fatal,
            _ => Serilog.Events.LogEventLevel.Information
        };
    }
}

// Usage
var serilogLogger = new LoggerConfiguration()
    .WriteTo.Console()
    .WriteTo.File("ironpdf-logs.txt")
    .CreateLogger();

IronSoftware.Logger.LoggingMode = IronSoftware.Logger.LoggingModes.Custom;
IronSoftware.Logger.CustomLogger = new SerilogAdapter(serilogLogger);
Enter fullscreen mode Exit fullscreen mode

Now all IronPDF logs flow through Serilog, inheriting your configured sinks (file, database, Application Insights, etc.).

How Do I Log to Application Insights?

Use the Microsoft.Extensions.Logging integration:

using Microsoft.ApplicationInsights;
using Microsoft.Extensions.Logging;
using System;

public class ApplicationInsightsLogger : ILogger
{
    private readonly TelemetryClient _telemetry;
    private readonly string _categoryName;

    public ApplicationInsightsLogger(TelemetryClient telemetry, string categoryName)
    {
        _telemetry = telemetry;
        _categoryName = categoryName;
    }

    public IDisposable BeginScope<TState>(TState state) => null;

    public bool IsEnabled(LogLevel logLevel) => true;

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
        Exception exception, Func<TState, Exception, string> formatter)
    {
        if (!IsEnabled(logLevel)) return;

        var message = formatter(state, exception);

        _telemetry.TrackTrace(message, ConvertLogLevel(logLevel), new Dictionary<string, string>
        {
            ["Category"] = _categoryName,
            ["EventId"] = eventId.ToString()
        });

        if (exception != null)
        {
            _telemetry.TrackException(exception);
        }
    }

    private Microsoft.ApplicationInsights.DataContracts.SeverityLevel ConvertLogLevel(LogLevel logLevel)
    {
        return logLevel switch
        {
            LogLevel.Trace => Microsoft.ApplicationInsights.DataContracts.SeverityLevel.Verbose,
            LogLevel.Debug => Microsoft.ApplicationInsights.DataContracts.SeverityLevel.Verbose,
            LogLevel.Information => Microsoft.ApplicationInsights.DataContracts.SeverityLevel.Information,
            LogLevel.Warning => Microsoft.ApplicationInsights.DataContracts.SeverityLevel.Warning,
            LogLevel.Error => Microsoft.ApplicationInsights.DataContracts.SeverityLevel.Error,
            LogLevel.Critical => Microsoft.ApplicationInsights.DataContracts.SeverityLevel.Critical,
            _ => Microsoft.ApplicationInsights.DataContracts.SeverityLevel.Information
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

This sends IronPDF logs to Application Insights with proper severity levels and custom properties.

Can I Add Context to Logs?

Yes. Enrich logs with request IDs, user info, or other context:

public class ContextualLogger : ILogger
{
    private readonly ILogger _innerLogger;
    private readonly string _requestId;

    public ContextualLogger(ILogger innerLogger, string requestId)
    {
        _innerLogger = innerLogger;
        _requestId = requestId;
    }

    public IDisposable BeginScope<TState>(TState state) => _innerLogger.BeginScope(state);

    public bool IsEnabled(LogLevel logLevel) => _innerLogger.IsEnabled(logLevel);

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
        Exception exception, Func<TState, Exception, string> formatter)
    {
        var originalMessage = formatter(state, exception);
        var enrichedMessage = $"[RequestId: {_requestId}] {originalMessage}";

        _innerLogger.Log(logLevel, eventId, enrichedMessage, exception, (msg, ex) => msg);
    }
}

// Usage
var requestId = Guid.NewGuid().ToString();
var contextualLogger = new ContextualLogger(baseLogger, requestId);

IronSoftware.Logger.CustomLogger = contextualLogger;
Enter fullscreen mode Exit fullscreen mode

This prefixes all IronPDF logs with a request ID, making it easy to trace operations in distributed systems.

How Do I Filter Log Levels?

Check the log level in IsEnabled:

public class FilteredLogger : ILogger
{
    private readonly LogLevel _minLevel;

    public FilteredLogger(LogLevel minLevel)
    {
        _minLevel = minLevel;
    }

    public bool IsEnabled(LogLevel logLevel) => logLevel >= _minLevel;

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
        Exception exception, Func<TState, Exception, string> formatter)
    {
        if (!IsEnabled(logLevel)) return;

        // Log only if level meets minimum threshold
        Console.WriteLine($"[{logLevel}] {formatter(state, exception)}");
    }

    public IDisposable BeginScope<TState>(TState state) => null;
}

// Only log warnings and above
IronSoftware.Logger.CustomLogger = new FilteredLogger(LogLevel.Warning);
Enter fullscreen mode Exit fullscreen mode

This reduces log volume in production while keeping error visibility.

Can I Log to a Database?

Yes. Write log entries to SQL or any database:

using System.Data.SqlClient;

public class DatabaseLogger : ILogger
{
    private readonly string _connectionString;

    public DatabaseLogger(string connectionString)
    {
        _connectionString = connectionString;
    }

    public IDisposable BeginScope<TState>(TState state) => null;

    public bool IsEnabled(LogLevel logLevel) => true;

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
        Exception exception, Func<TState, Exception, string> formatter)
    {
        if (!IsEnabled(logLevel)) return;

        var message = formatter(state, exception);

        using (var conn = new SqlConnection(_connectionString))
        {
            conn.Open();
            var cmd = new SqlCommand(@"
                INSERT INTO ApplicationLogs (Timestamp, Level, Message, Exception)
                VALUES (@timestamp, @level, @message, @exception)", conn);

            cmd.Parameters.AddWithValue("@timestamp", DateTime.UtcNow);
            cmd.Parameters.AddWithValue("@level", logLevel.ToString());
            cmd.Parameters.AddWithValue("@message", message);
            cmd.Parameters.AddWithValue("@exception", exception?.ToString() ?? (object)DBNull.Value);

            cmd.ExecuteNonQuery();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This persists logs for audit trails or long-term analysis.

How Do I Test Custom Logging?

Create a test logger that captures messages:

using System.Collections.Generic;

public class TestLogger : ILogger
{
    public List<string> LoggedMessages { get; } = new List<string>();

    public IDisposable BeginScope<TState>(TState state) => null;

    public bool IsEnabled(LogLevel logLevel) => true;

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
        Exception exception, Func<TState, Exception, string> formatter)
    {
        LoggedMessages.Add(formatter(state, exception));
    }
}

// Usage in tests
var testLogger = new TestLogger();
IronSoftware.Logger.LoggingMode = IronSoftware.Logger.LoggingModes.Custom;
IronSoftware.Logger.CustomLogger = testLogger;

// Run PDF operation
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Test</h1>");

// Assert logs
Assert.IsTrue(testLogger.LoggedMessages.Any(m => m.Contains("Rendering")));
Enter fullscreen mode Exit fullscreen mode

This validates that expected operations log correctly.

Can I Disable Logging Entirely?

Set logging mode to None:

IronSoftware.Logger.LoggingMode = IronSoftware.Logger.LoggingModes.None;
Enter fullscreen mode Exit fullscreen mode

This disables all IronPDF logging for maximum performance in scenarios where logs aren't needed.

What Log Levels Does IronPDF Use?

  • Trace: Detailed diagnostic information
  • Debug: Development-time debugging info
  • Information: General operational messages
  • Warning: Recoverable issues (e.g., deprecated API usage)
  • Error: Operation failures
  • Critical: Severe errors requiring immediate attention

In production, I typically filter to Information and above to reduce noise.

How Do I Log to Multiple Destinations?

Create a composite logger:

public class CompositeLogger : ILogger
{
    private readonly List<ILogger> _loggers;

    public CompositeLogger(params ILogger[] loggers)
    {
        _loggers = new List<ILogger>(loggers);
    }

    public IDisposable BeginScope<TState>(TState state) => null;

    public bool IsEnabled(LogLevel logLevel) => _loggers.Any(l => l.IsEnabled(logLevel));

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
        Exception exception, Func<TState, Exception, string> formatter)
    {
        foreach (var logger in _loggers)
        {
            if (logger.IsEnabled(logLevel))
            {
                logger.Log(logLevel, eventId, state, exception, formatter);
            }
        }
    }
}

// Usage
var consoleLogger = new ConsoleLogger();
var fileLogger = new FileLogger("ironpdf.log");
var composite = new CompositeLogger(consoleLogger, fileLogger);

IronSoftware.Logger.CustomLogger = composite;
Enter fullscreen mode Exit fullscreen mode

Logs now go to both console and file simultaneously.


Written by Jacob Mellor, CTO at Iron Software. Jacob created IronPDF and leads a team of 50+ engineers building .NET document processing libraries.

Top comments (0)