OP-08: Post a log entry to the Logging API¶
| Field | Value |
|---|---|
| Operation ID | OP-08 |
| Trigger | The Serilog pipeline dispatches a log event to LoggingApiSink (attached for web and Windows Service hosts only). |
| Inputs | The LogEvent being emitted; the configured base URL, route, error route, and output template. |
| Outputs | An HTTP POST to the Logging API; on an unsuccessful HTTP result, a Console/Debug-only diagnostic. |
| Primary components | LoggingApiSink |
| Entry point | LoggingApiSink.Emit |
Steps¶
- Before any event is emitted, the sink's constructor runs
_httpJsonClient.SetClientInfo(baseUrl, "SaifLog", Environment.MachineName, _applicationId)and then builds_messageFormatter = new MessageTemplateTextFormatter(logPattern, null), both unconditionally and with no surrounding try/catch in this library's code (BR-LOG-14). If either call throws — for example, if the specificIHttpJsonClientimplementation rejects an empty/invalidbaseUrl, orMessageTemplateTextFormatterrejects anull/malformedlogPattern— the sink object itself is never fully constructed, so it is never attached to the Serilog pipeline andEmit(and therefore neitherSendnorSendException) ever runs for that construction attempt. Emit(LogEvent logEvent)(anasync voidmethod with no try/catch of its own) checksIsException(logEvent)—logEvent.Exception != null && logEvent.Properties.ContainsKey(StringValues.LogExceptionProperty)— not a switch on an incoming DTO type (BR-LOG-13).- When
IsExceptionistrue,SendExceptionbuilds the error-route URI (suffixed?errorId={LogId}whenlogEvent.LogId()is non-blank) and POSTslogEvent.LogExceptionDetail()— theErrorLogItemextracted from the event's properties, not a rendered message string; this branch never calls_messageFormatter. - When
IsExceptionisfalse,Sendrenders the event through_messageFormatter(built once in step 0, from the configured output template — standard orappsettings.json-overridden) and POSTs the resultingLogItemto the non-error route (BR-LOG-14). - After either branch completes,
Emitunconditionally runsHandleHttpPostError, which checksIHttpJsonClient.RequestResult.IsSuccessfuland writes a diagnostic toConsole/Debugonly when it isfalse(BR-LOG-15). - An exception thrown by the
PostAsynccall itself (as opposed to an unsuccessful result) is not caught anywhere in this sink. BecauseEmitisasync voidrather thanasync Task, that exception is not captured on aTaskfor later observation — it propagates directly to the SynchronizationContext captured whenEmitstarts, or the thread pool if none was captured, rather than being reported by this library at all.
Process flow¶
flowchart TD
A[LogEvent dispatched to LoggingApiSink.Emit] --> B{"IsException: Exception != null AND<br>LogExceptionDetails property present?"}
B -->|Yes| C["SendException: POST logEvent.LogExceptionDetail()<br>to error route + errorId"]
B -->|No| D["Send: render via output template,<br>POST LogItem to non-error route"]
C --> E[HandleHttpPostError checks RequestResult.IsSuccessful]
D --> E
E -->|Unsuccessful| F[Write diagnostic to Console/Debug only]
E -->|Successful| G[No further action]
Failure and edge-case behavior¶
- An empty base URL results in a POST to a potentially invalid endpoint only if the sink's construction in step 0 succeeds;
SetClientInfoand theMessageTemplateTextFormatterconstructor run first, with no surrounding try/catch, and if either throws the sink is never fully constructed, so no POST is attempted for that instance at all.SetClientInforeceives only the base URL and client identity — not either route — so an empty base URL is the only construction-time input that can affect the final endpoint; the route and error route are stored and passed separately as thePostAsyncURI argument, and final endpoint validity for an empty route depends entirely on how the injectedIHttpJsonClientcombines it with the base URL, which this library's source does not determine or confirm. When construction does succeed, an unsuccessful result from the resulting POST is reported only to Console/Debug, never surfaced to the original caller (BR-LOG-15). - A thrown exception from
PostAsync(network failure, serialization error, etc.) is not caught anywhere inLoggingApiSink; there is no universal failure-containment guarantee — only unsuccessful HTTP results are inspected and reported. SaifWindowsServiceLogalways posts errors with anullroute component, since it never readslogerrorroute(BR-LOG-09).- A
nulloutput template (from an override entry missingLogPattern) is passed into the sink's constructor at construction time (new MessageTemplateTextFormatter(logPattern, null)), before either branch runs, not merely intoSend's per-event rendering step; whetherMessageTemplateTextFormatter's own constructor accepts anulltemplate without throwing is not confirmed in this library's source.** If that constructor call throws, the sink is never fully constructed, so neitherSendnorSendExceptionruns at all for that attempt — there is no case where construction fails butSendExceptionstill executes unaffected. If construction instead succeeds (anulltemplate is tolerated),SendExceptionnever applies_messageFormatterand so its payload shape is unaffected by the template's value, same as always.
Related documentation¶
Note: Statements marked with
**could not be confirmed; treat as unknown until verified.
Sources¶
src/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SAIF.NetCore.Logging/Loggers/LoggingApiSink.cs