---
title: "OP-01: Configure the web logger (SaifLog)"
summary: Bootstrap sequence for the ASP.NET Core SAIF logger.
status: draft
owner: SAIF Platform Team
last_reviewed: 2026-10-02
library: SAIF.NetCore.Logging
reference_type: package
operation_id: OP-01
trigger: Host application calls SaifLog.Configure(IEnvironmentClient environmentClient, IHttpJsonClient loggingHttpClient, IHttpContextAccessor httpContext) (typically once, at startup) or the constructor path that invokes it.
---

# OP-01: Configure the web logger (SaifLog)

| Field | Value |
| --- | --- |
| Operation ID | OP-01 |
| Trigger | Host application calls `SaifLog.Configure(IEnvironmentClient, IHttpJsonClient, IHttpContextAccessor)` during application startup. |
| Inputs | `IEnvironmentClient` (Environment API access), `IHttpJsonClient` (Logging API transport), `IHttpContextAccessor` (used to build the web-specific exception formatter). |
| Outputs | A configured Serilog pipeline with Event Log and Console sinks plus the Logging API sink; `IsConfigured` set to `true`. |
| Primary components | `SaifLog`, `SaifLogBase`, `ApiLoggerConfig`, `EventLogConfig` |
| Entry point | `SaifLog.Configure` |

## Steps
1. Call `ConfigureLoggers(environmentClient)`, which resolves the base log threshold by checking the application-named Environment API section first, falling back to `GetSettingValue("logthreshold", "tracingdefaults")` ([BR-LOG-01](../business-rules.md#br-log-01-application-threshold-overrides-organization-wide-default), [BR-LOG-02](../business-rules.md#br-log-02-numeric-level-strings-bypass-enum-validation)).
2. Parse the resolved threshold string into a `LogEventLevel`; an unparseable or empty value throws `ArgumentException` and aborts configuration ([BR-LOG-08](../business-rules.md#br-log-08-log-entries-drop-until-logger-is-configured)).
3. Still inside `ConfigureLoggers`: build the standard `ApiLoggerConfig` and `EventLogConfig` sink configurations from the Environment API pattern keys and the resolved threshold, then apply the `SaifLogging:BaseLevel` and `SaifLogging:Loggers` `appsettings.json` overrides:
   - 3.1. Check whether the `SaifLogging` section exists **and** contains a `BaseLevel` child; only then replace `BaseLevel` with that configured value (normalized via `ToLogLevelString()`) — an absent section or key leaves the Environment-API-derived `BaseLevel` from steps 1–2 untouched ([BR-LOG-02](../business-rules.md#br-log-02-numeric-level-strings-bypass-enum-validation)).
   - 3.2. Check whether `SaifLogging:Loggers` exists; for each child entry, parse `Type`/`Level`/`LogPattern` and replace the matching standard `ApiLog`/`EventLog` config wholesale, throwing `SaifLogConfigurationException` for an invalid `Type` or `ArgumentException` for a missing `Level` ([BR-LOG-03](../business-rules.md#br-log-03-logger-type-matching-is-case-sensitive), [BR-LOG-04](../business-rules.md#br-log-04-missing-override-level-throws-configuration-error), [BR-LOG-05](../business-rules.md#br-log-05-invalid-logger-type-throws-configuration-exception)).

   > **Origin**: Steps 3.1–3.2 are implemented in [Apply appsettings.json logger overrides](op-04-apply-logger-overrides.md) (`SaifLogBase.ApplyLoggingSettingOverrides`) and executed on behalf of this operation.
4. Back in `Configure`: set `ExFormatService` to a new `ExceptionFormatServiceForWeb(environmentClient, httpContext)` and call `Log.CloseAndFlush()`.
5. Read `tracingdefaults:loggingapiurl`, `tracingdefaults:logroute`, and `tracingdefaults:logerrorroute` from the Environment API, and push the base URL/credentials into the injected `IHttpJsonClient`.
6. Construct the Serilog logger with the resolved `BaseLevelSwitch`, an Event Log sink at the configured `EventLogConfig` level, a `LoggingApiSink`, and a Console sink.
7. Set `IsConfigured = true` so that subsequent log calls are accepted.

## Process flow

```mermaid
flowchart TD
    A[Configure called] --> B["ConfigureLoggers: resolve base threshold"]
    B --> C{Threshold valid?}
    C -->|No| C1["ArgumentException - Configure aborts,<br>IsConfigured stays false if this is the first<br>attempt, or keeps its prior true value<br>if a previously-successful Configure is retried"]
    C -->|Yes| D["Build standard ApiLoggerConfig + EventLogConfig,<br>apply BaseLevel/Loggers overrides"]
    D --> E["Set ExFormatService = ExceptionFormatServiceForWeb"]
    E --> F[Read loggingapiurl / logroute / logerrorroute]
    F --> G[Push base URL into IHttpJsonClient]
    G --> H["Build Serilog logger: Event Log sink +<br>LoggingApiSink + Console sink"]
    H --> I["IsConfigured = true"]
```

## Failure and edge-case behavior
- A missing or invalid base threshold aborts this `Configure` call entirely; `IsConfigured` stays `false` if this is the first `Configure` attempt on the instance, or keeps its prior `true` value if a previously-successful `Configure` is being retried and this one fails ([BR-LOG-08](../business-rules.md#br-log-08-log-entries-drop-until-logger-is-configured)). While `IsConfigured` is `false`, every subsequent non-exception log call still returns a generated correlation ID but writes only a Debug-level diagnostic trace instead of reaching `ILogger`/Serilog; an exception-bearing call returns an ID only once its own pre-ID exception-formatting steps complete without throwing.
- An invalid `SaifLogging:Loggers` entry `Type` throws `SaifLogConfigurationException`; a missing `Level` throws `ArgumentException` — both abort this `Configure` call the same way.
- Whether a missing `loggingapiurl`/`logroute`/`logerrorroute` aborts configuration is not confirmed one way or the other in this library's source for every case.** `LoggingApiSink`'s constructor calls `_httpJsonClient.SetClientInfo(baseUrl, "SaifLog", Environment.MachineName, applicationId)` — only the base URL and client identity, not either route — and separately stores `routePart`/`errorRoutePart` for later use as the `PostAsync` URI argument; this call happens without a surrounding try/catch. Whether the specific `IHttpJsonClient` implementation in use throws for an empty/invalid `baseUrl` argument is implementation-dependent and not something this library's own source confirms either way. If `SetClientInfo` (or the sink constructor itself) does not throw, final endpoint validity for the subsequent `PostAsync(routePart, ...)` call depends entirely on how the injected `IHttpJsonClient` resolves a base URL and route together — an empty route does not by itself make a valid base URL resolve to an invalid or empty endpoint. An unsuccessful HTTP result from that POST is reported only to Console/Debug ([BR-LOG-15](../business-rules.md#br-log-15-emit-is-async-void-with-no-exception-containment)); separately, `LoggingApiSink.Emit` is `async void` with no try/catch, so an exception thrown by the POST call itself (as opposed to an unsuccessful result) is not caught or reported anywhere in this library.

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

> **Note:** Statements marked with `**` could not be confirmed; treat as unknown until verified.

## Sources
- `src/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SaifLog.cs`
- `src/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SaifLogBase.cs`
- `src/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SaifLoggerConfig.cs`
