# How SharpEmu's Composite Logging System Routes Messages to Multiple Sinks

> Explore how SharpEmu's composite logging system routes messages to multiple sinks. Discover fault-tolerant log delivery with CompositeLogSink.

- Repository: [Berk/sharpemu](https://github.com/par274/sharpemu)
- Tags: internals
- Published: 2026-07-13

---

**SharpEmu uses a `CompositeLogSink` class that implements `ISharpEmuLogSink` and forwards each log entry to an array of child sinks, swallowing individual exceptions to ensure fault-tolerant delivery across all configured destinations.**

The `par274/sharpemu` repository implements a lightweight, extensible logging framework designed specifically for emulator debugging scenarios. Its **composite logging system** solves the common problem of routing identical log output to multiple destinations—such as the console and a file—without duplicating logging calls throughout the codebase. The architecture centers on a minimal interface contract and a fault-tolerant dispatcher that guarantees delivery even when individual sinks fail.

## Core Components of the Logging Pipeline

The system relies on three primary abstractions: the sink interface, the composite dispatcher, and the immutable log entry structure.

### The Sink Interface (ISharpEmuLogSink)

Every destination implements `ISharpEmuLogSink`, defining a single method that receives log entries by reference to avoid unnecessary struct copying:

```csharp
public interface ISharpEmuLogSink
{
    void Write(in LogEntry entry);
}

```

*Source:* [[`src/SharpEmu.Logging/ISharpEmuLogSink.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Logging/ISharpEmuLogSink.cs)](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Logging/ISharpEmuLogSink.cs)

### The CompositeLogSink Dispatcher

The `CompositeLogSink` class acts as a **fan-out router**, maintaining a private readonly array of child sinks and iterating through them for every log entry. According to the source code in [`CompositeLogSink.cs`](https://github.com/par274/sharpemu/blob/main/CompositeLogSink.cs), it implements both `ISharpEmuLogSink` and `IDisposable` to manage resource cleanup across its children.

## How the CompositeLogSink Implements Fault Tolerance

The critical feature of SharpEmu's composite logging system is its **exception isolation**. When `Write` is called, the method wraps each child sink invocation in an empty catch block, ensuring that a failure in one sink (such as a locked file or closed stream) never terminates the loop or affects other sinks:

```csharp
public sealed class CompositeLogSink : ISharpEmuLogSink, IDisposable
{
    private readonly ISharpEmuLogSink[] _sinks;
    
    public void Write(in LogEntry entry)
    {
        foreach (var sink in _sinks)
        {
            try { sink.Write(in entry); }
            catch { /* swallow so other sinks keep working */ }
        }
    }
    
    public void Dispose()
    {
        foreach (var sink in _sinks)
        {
            if (sink is IDisposable disposable)
                disposable.Dispose();
        }
    }
}

```

*Source:* [[`src/SharpEmu.Logging/CompositeLogSink.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Logging/CompositeLogSink.cs)](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Logging/CompositeLogSink.cs)

This design provides **fail-safe logging** where a network sink can crash without corrupting your file-based audit trail.

## Configuring the Default Sink Chain

The static façade `SharpEmuLog` automatically constructs the sink chain at startup based on environment variables. The `ResolveSinkFromEnvironment()` method determines whether to create a single console sink or a composite setup:

```csharp
private static ISharpEmuLogSink ResolveSinkFromEnvironment()
{
    var consoleSink = new ConsoleLogSink(
        useColors: ResolveColorEnabledFromEnvironment(),
        includeTimestamp: false);

    var logFilePath = Environment.GetEnvironmentVariable("SHARPEMU_LOG_FILE");
    if (!string.IsNullOrWhiteSpace(logFilePath))
    {
        var fileSink = new FileLogSink(logFilePath, append: true, includeTimestamp: true);
        _fileCapturesAllLevels = true;
        return new CompositeLogSink(new MinimumLevelFilterSink(consoleSink), fileSink);
    }

    return consoleSink;
}

```

*Source:* [[`src/SharpEmu.Logging/SharpEmuLog.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Logging/SharpEmuLog.cs) (lines 96-112)](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Logging/SharpEmuLog.cs#L96-L112)

When the `SHARPEMU_LOG_FILE` environment variable is present, the system creates a **composite sink** containing both a filtered console sink and an unfiltered file sink. This allows the console to show only warnings and errors while the file captures every debug message.

## Filtering and LogEntry Structure

### MinimumLevelFilterSink Implementation

The `MinimumLevelFilterSink` is a **decorator** that wraps another sink and applies the global `_minimumLevel` threshold before forwarding. This enables different verbosity levels per destination within the composite:

```csharp
private sealed class MinimumLevelFilterSink : ISharpEmuLogSink
{
    private readonly ISharpEmuLogSink _inner;
    
    public void Write(in LogEntry entry)
    {
        if (entry.Level >= _minimumLevel)
            _inner.Write(in entry);
    }
}

```

*Source:* [[`src/SharpEmu.Logging/SharpEmuLog.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Logging/SharpEmuLog.cs) (lines 27-38)](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Logging/SharpEmuLog.cs#L27-L38)

### The Immutable LogEntry Record

All sinks receive the same `LogEntry` struct, passed by `in` to optimize performance:

```csharp
public readonly record struct LogEntry(
    DateTimeOffset Timestamp,
    LogLevel Level,
    string Category,
    string Message,
    string SourceFileName,
    int SourceLine,
    string SourceMemberName,
    Exception? Exception = null);

```

*Source:* [[`src/SharpEmu.Logging/LogEntry.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Logging/LogEntry.cs)](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Logging/LogEntry.cs)

Because `LogEntry` is a `readonly record struct`, it provides **value semantics** with immutability guarantees while avoiding heap allocations when passed by reference.

## Practical Implementation Example

To configure a custom composite logging system with both console and file outputs programmatically:

```csharp
using SharpEmu.Logging;

// Create individual sinks
var console = new ConsoleLogSink(useColors: true, includeTimestamp: false);
var file = new FileLogSink("logs/emulator.log", append: true, includeTimestamp: true);

// Combine into composite sink
var composite = new CompositeLogSink(console, file);

// Configure the global logger
SharpEmuLog.Configure(minimumLevel: LogLevel.Debug, sink: composite);

// Obtain a category-specific logger
var log = SharpEmuLog.For("Emulator.Core");

// Each message routes to both destinations
log.Info("Emulator initialized");
log.Debug("Loading ROM segment");

```

This pattern allows you to extend the pipeline with additional sinks—such as network loggers or in-memory buffers—simply by implementing `ISharpEmuLogSink` and adding instances to the `CompositeLogSink` constructor.

## Summary

- **SharpEmu** uses `CompositeLogSink` to route single log entries to multiple destinations simultaneously.
- The `Write` method implements **fault tolerance** by catching and swallowing exceptions from individual sinks, preventing cascade failures.
- **Environment-based configuration** automatically creates composite sinks when `SHARPEMU_LOG_FILE` is set, combining `ConsoleLogSink` and `FileLogSink`.
- **Level filtering** is applied per-sink via `MinimumLevelFilterSink`, allowing files to capture all levels while the console shows only important messages.
- All sinks receive the same **immutable `LogEntry`** struct passed by `in` for performance efficiency.

## Frequently Asked Questions

### What happens if one sink throws an exception in SharpEmu's composite logging system?

The `CompositeLogSink.Write` method catches exceptions individually within its `foreach` loop and swallows them silently. This ensures that a failure in one sink—such as a `FileLogSink` encountering a locked file—does not prevent other sinks from receiving the log entry.

### How does SharpEmu filter log levels differently for console vs file outputs?

When both sinks are active, the console sink is wrapped in a `MinimumLevelFilterSink` decorator that checks the global minimum level before forwarding, while the file sink is added directly to the composite without filtering. This architecture allows the file to capture all log levels while the console respects verbosity settings.

### Can I add custom sinks to SharpEmu's logging system?

Yes. Any class implementing `ISharpEmuLogSink` can be instantiated and passed to the `CompositeLogSink` constructor alongside built-in sinks like `ConsoleLogSink` or `FileLogSink`. The system treats all sinks uniformly through the interface contract.

### Is the LogEntry struct passed by reference or value?

The `LogEntry` struct is passed **by reference** using the `in` parameter modifier in the `Write` method signature (`void Write(in LogEntry entry)`). This prevents copying the struct while maintaining read-only semantics for thread safety.