Skip to main content

Logging

Two parallel logging systems live in WmsSyncHubApi:

LayerSinkLevelWhen to use
Seriloglogs/wmssynchubapi-YYYYMMDD.logWarning+ (configurable)High-volume, structured, local file
ErrorLoggerZ_WmsErrorLog SQL tableWarn / ErrorBusiness-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.jsonSerilog.WriteTo.File.path.

ErrorLoggerZ_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.jsonLogAccess.ApiKey.

EndpointPurpose
GET /api/logsQuery Z_WmsErrorLog with filters (from, to, level, source, limit).
GET /api/logs/summaryCounts of errors / warnings per day, per source.
GET /api/logs/filesList of Serilog files on disk.
GET /api/logs/file/{name}?tail=NLast 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_WmsSyncLog is 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. :::