How We Solved a Tough Logging Bug in Site Informant – Serilog, SQLite, and EF Core Concurrency
Published December 2025 by Site Informant Team
Every once in a while, a deceptively small issue turns into a deep technical adventure. This week, our team ran into a thorny logging failure that involved Serilog’s Durable HTTP Sink, SQLite WAL mode, JSON payload formatting, and a surprisingly tricky EF Core DbContext concurrency problem triggered by background processing.
The short version? Our logs stopped flowing — silently — and the root cause was a perfect storm of JSON formatting, SQLite locking behavior, and concurrent background tasks.
What We Saw First
The Site Informant logging dashboard showed only two rows: a manual test message and an early boot log. Everything else was missing.
But the Serilog durable buffer was filling up — and the serilog-selflog.txt file
revealed a repeating error:
BadRequest: The logs field is required.
Invalid JSON payload.
The durable sink was sending batches, but our API rejected them. At first glance, the JSON looked correct… but the issue ran deeper.
Issue #1 – JSON Not Matching the Expected Model
Serilog's durable sink sends logs as newline-delimited JSON, not a JSON array. Our API expected:
[
{ "Timestamp": "...", "Level": "...", ... }
]
But the buffer contained:
{"Timestamp":"..."}
{"Timestamp":"..."}
{"Timestamp":"..."}
Once we added a custom ArrayBatchFormatter to wrap each batch in [ ... ],
the API was finally willing to accept the payload.
Issue #2 – SQLite WAL Mode + Heavy Write Workloads
Our logger uses SQLite with WAL mode for concurrency. But WAL mode must be explicitly enabled on the deployed file — not just in development.
Once the production VM was updated with:
PRAGMA journal_mode = WAL;
The logger API was finally able to handle high write volume without locking the database.
Issue #3 – EF Core Concurrency in Background Services
Even after fixing logging, some messages in the buffer pointed to another problem:
A second operation was started on this context instance before a previous operation completed.
The culprit?
Our SiteStatusProcessorService was performing up to 10 parallel checks, but each
instance of SiteStatusDomainService shared a single injected DbContext.
This caused overlapping writes on the same context instance — something EF Core does not allow.
The fix was simple and elegant:
- Create a new DI scope for each background iteration
- Resolve a fresh
SiteStatusDomainService(and therefore a fresh DbContext)
With that, all concurrency-related failures disappeared.
Lessons Learned
- Durable logging is only as good as your batch format.
- SQLite requires special configuration for production concurrency.
- EF Core DbContext is not thread-safe — ever.
- Background services should create fresh scopes for every parallel batch.
In the end, we not only fixed the logging system — we strengthened the entire monitoring pipeline. The result is faster, more resilient, and far easier to debug going forward.
What’s Next
Now that durable logging is fully operational, we’re rolling out:
- Slack + Discord integrations
- More advanced alert throttling
- Real-time visual logs inside the dashboard
- API endpoints for developers to ingest log streams directly
Monitor your sites for free: Try Site Informant