Logging
Two parallel logging systems live in WmsSyncHubApi:
| Layer | Sink | Level | When to use |
|---|---|---|---|
| Serilog | logs/wmssynchubapi-YYYYMMDD.log | Warning+ (configurable) | High-volume, structured, local file |
ErrorLogger | Z_WmsErrorLog SQL table | Warn / Error | Business-visible, remotely queryable |
Serilog (file)
Configured in WmsSyncHubApi/Logging/SerilogConfig.cs. Each worker
holds an ILogger<T> and emits:
_log.LogInformation("PickingNoteWorker pushed {PN} → WMS", pickingNoteNo);
_log.LogWarning("Retrying WMS push for {PN}, attempt {N}", pn, n);
_log.LogError(ex, "Unhandled in PickingNoteWorker");
When to reach for it: anything the developer would want while
reading logs — poll timings, request/response snippets, exceptions
with stack traces. Minimum level is Warning in production to keep
files small; Debug / Information when running dotnet run locally
(the appsettings.Development.json overrides raise the floor).
File rotation: daily, 7 days retained. Files live under whatever
base path the API's working directory resolves to — check
appsettings.json → Serilog.WriteTo.File.path.
ErrorLogger → Z_WmsErrorLog
WmsSyncHubApi/Logging/ErrorLogger.cs:
ErrorLogger.Warn("PickingNoteWorker", $"WMS returned {code}: {msg}");
ErrorLogger.Error("AsnCreateWorker", ex);
Writes to Z_WmsErrorLog(ErrorLogId, CreatedOn, Source, Level, Message, Exception, StackTrace).
When to reach for it: when the error needs to be visible to a
non-developer (support engineer, power user) via SQL, or surfaced in
a future admin UI. Rule of thumb: if the user might be asked "what's
the error code?", log via ErrorLogger.
Both layers should be used together — most error sites call
_log.LogError and ErrorLogger.Error for the same event.
Remote log access — /api/logs*
Four endpoints on the API for remote diagnostics. All require the
x-api-key header matching appsettings.json →
LogAccess.ApiKey.
| Endpoint | Purpose |
|---|---|
GET /api/logs | Query Z_WmsErrorLog with filters (from, to, level, source, limit). |
GET /api/logs/summary | Counts of errors / warnings per day, per source. |
GET /api/logs/files | List of Serilog files on disk. |
GET /api/logs/file/{name}?tail=N | Last N lines of a given Serilog file. |
Example curl
API=https://wms-sync.prismatechnology.com.my
KEY=your-api-key-from-appsettings
# Last 100 errors in the DB
curl -H "x-api-key: $KEY" "$API/api/logs?level=Error&limit=100"
# Today's summary
curl -H "x-api-key: $KEY" "$API/api/logs/summary"
# List files
curl -H "x-api-key: $KEY" "$API/api/logs/files"
# Tail last 200 lines of today's file
curl -H "x-api-key: $KEY" \
"$API/api/logs/file/wmssynchubapi-20260423.log?tail=200"
appsettings.json
{
"LogAccess": {
"ApiKey": "<rotate-me-on-deploy>"
}
}
Guidance
- Prefer structured logging (
{Placeholder}) over string concatenation — Serilog indexes the properties. - Never log
partnerKey. The gateway client sanitises it out of request logs already; do not re-add it in a catch handler. - Raw WMS payloads go to
Z_WmsSyncLog.RawPayload, not Serilog. Serilog is for developer observability;Z_WmsSyncLogis the business audit trail.
Caution
:::caution Z_WmsErrorLog and Z_WmsSyncLog grow without bound Neither table has an automatic purge. A nightly SQL job that deletes rows older than 90 days is the recommended deployment pattern — especially on customer sites where the API might run unattended for months. :::
:::caution LogAccess.ApiKey is effectively a bearer token Rotate on every deploy. Anyone with the key can read every customer order ever synced. The endpoints are not rate-limited; if the API is publicly reachable (behind the reverse proxy), keep the key short-lived and treat it like a secret. :::