---
title: "OP-09: Write a console log entry"
summary: How console hosts render log entries to the terminal and auxiliary files.
status: draft
owner: SAIF Platform Team
last_reviewed: 2026-10-02
library: SAIF.NetCore.Logging
reference_type: package
operation_id: OP-09
trigger: Serilog dispatches a standard log event to the ConsoleLogService's Console/Event Log sinks, or the host explicitly calls WriteToAppLog or GetLog(logType).Write* against one of the five auxiliary LogWriter file channels.
---

# OP-09: Write a console log entry

| Field | Value |
| --- | --- |
| Operation ID | OP-09 |
| Trigger | Two independent paths: (1) the Serilog pipeline built in `ConsoleLogService.CreateLogger()` dispatches a standard `Debug`/`Info`/`Warning`/`Error`/`Fatal` call to the Console sink (and the Event Log sink when the event is Fatal); (2) the host explicitly calls `ConsoleLogService.WriteToAppLog(message, logType)` or `GetLog(logType).Write*` against one of the five registered `LogWriter` file channels — this is not automatically triggered by the standard log methods. |
| Inputs | The formatted log event and the `BaseLevelSwitch` threshold (path 1); the message text and target `LogType` (path 2). |
| Outputs | A console line (and, for Fatal events, a Windows Event Log entry); separately, an appended line in the explicitly targeted log file. |
| Primary components | `ConsoleLogService`, `LogWriter` |
| Entry point | Serilog sink dispatch for the main pipeline (no public method call); `ConsoleLogService.WriteToAppLog` / `GetLog(LogType)` for the file channel |

## Steps
1. For the main pipeline: Serilog evaluates the event against `BaseLevelSwitch`, resolved solely from `SaifLogging:BaseLevel` (default `Info`) — Environment API thresholds and `appsettings.json` `Loggers` overrides have no effect here ([BR-LOG-12](../business-rules.md#br-log-12-console-logger-threshold-ignores-environment-api)).
2. The Console sink renders and writes every event that passes the threshold to standard output/error; the Event Log sink additionally writes the event only when its level is `Fatal` (hardcoded in `CreateLogger()`).
3. For the file channel (independent of steps 1–2): the host calls `WriteToAppLog(message, logType)` or obtains the `ILogWriter` via `GetLog(logType)` and calls one of its `Write`/`WriteError`/`WriteDebug`/`WriteWarning` methods.
4. Each `Write*` call first calls `SetActivityDetails()`, tagging `Activity.Current` with `log.type` (the `LogType`) and `log.file` (the writer's `FilePath`).
5. It then writes to the injected `ILogger<ISaifLoggable>` with the template `"[{LogType}] {Message}"` — this write goes wherever the host's own `ILogger<ISaifLoggable>` provider is configured to send it, which is **not** necessarily this writer's file.
6. Only then does it access the `Logger` property to write the Serilog message: this access is what lazily triggers `Configure()` on first use if the underlying logger has not been built yet, opening the file at the configured (or defaulted) path and applying its own independent `LoggingLevelSwitch` fixed at `LogEventLevel.Verbose` — this threshold is unrelated to `BaseLevelSwitch`. Only this Serilog `Logger` instance writes to the channel's actual file; the `ILogger<ISaifLoggable>` write in the previous step is a separate destination.

## Process flow

```mermaid
flowchart TD
    A["Standard log call (Debug/Info/Warning/Error/Fatal)"] --> B{Meets BaseLevelSwitch threshold?}
    B -->|No| Z[Event suppressed]
    B -->|Yes| C[Console sink writes to stdout/stderr]
    C --> D{Event level is Fatal?}
    D -->|Yes| E[Event Log sink also writes the entry]
    D -->|No| F[Event Log sink skips it]
    G["WriteToAppLog / GetLog(LogType).Write* called explicitly"] --> H["SetActivityDetails: tag Activity.Current<br>with log.type and log.file"]
    H --> I["Write to injected ILogger (separate destination,<br>not necessarily this writer's file)"]
    I --> J["Access Logger property: lazily runs<br>LogWriter.Configure on first use (Verbose switch)"]
    J --> K[Write message to this Serilog Logger - appends to the file]
```

## Failure and edge-case behavior
- The file channel is only reached through an explicit `WriteToAppLog` or `GetLog(logType).Write*` call; it is not a destination that standard `Debug`/`Info`/`Warning`/`Error`/`Fatal` calls route through automatically, and it is not an automatic sink fan-out from the main Console/Event Log pipeline.
- Only the channel's own Serilog `Logger` instance writes to that channel's file; the injected `ILogger<ISaifLoggable>` write in each `Write*` call reaches whatever destination the host's own `ILogger` provider is configured for, which is not guaranteed to be this file.
- Because file sinks are lazily configured on first access of the `Logger` property (which, within a `Write*` call, happens after the `ILogger<ISaifLoggable>` write) **or** via an explicit call to the writer's own public `Configure()` method, a channel that is never explicitly `Configure()`-d and whose `Logger` property is never accessed never creates a file on disk — access, not a write specifically, is what triggers creation.
- The default Windows-style path (`C:/temp/logs/file{n}.txt`) is used whenever the corresponding `file{n}` key is unset, which can be inappropriate on non-Windows hosts ([BR-LOG-18](../business-rules.md#br-log-18-console-file-sinks-default-to-local-temp-path)).

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)

## Sources
- `src/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SAIF.NetCore.Logging/ConsoleLogService.cs`
- `src/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SAIF.NetCore.Logging/Console/LogWriter.cs`
