# Threaded asynchronous logging A console program that goes deeper than the [first logger](https://youseful.software/delphi/projects/atlas-log-firstlogger.html): several threads, a background writer with a bounded queue, and what happens when the target cannot keep up. Every result is checked. ## What it demonstrates Part 1, four threads and one JSON file: - Four worker threads log 500 debug events and one warning each through one `TAtlasLogger`, each inside its own `BeginScope`. - A `TAtlasAsyncSink` (`Capacity` 256, `Overflow := ofBlock`) queues the events and writes them on its background thread to a `TAtlasFileSink` with `Layout := loJson`. The file sink is only the `Target`; it is not attached to the logger itself. - A `TAtlasCallbackSink` with `MinLevel := llWarning` whose `OnEvent` handler runs on the thread that logged. - A custom sink, `TLevelCounter`, written by overriding `TAtlasLogSink.DoWrite`. - `Log.Flush` waits until the queue is empty and the file is flushed. The program then parses all 2004 lines with `System.JSON` and checks that nothing was dropped, sequence numbers are unique, and each thread's steps are in order under that thread's scope. Part 2, a target that is held back: - A second `TAtlasAsyncSink` with `Capacity` 10 writes to a callback sink whose handler waits on an event, so the queue fills. - `ofDropNewest` discards what does not fit (19 or 20 of 30 events, depending on whether the background thread had already taken the first one) and counts them in `Dropped`; the oldest events are written. - `ofDropOldest` discards from the front and keeps the newest event. - A target that raises (a `TAtlasFileSink` without a `FileName`) is counted in `TargetErrors`; nothing is raised into the program and the logger's `SinkErrors` stays 0. ## Build and run Open `ThreadedAsyncLogging.dpr` in Delphi 13; the IDE creates the project file. Add the Atlas Log `src` folder to the project's search path, then build and run for Win32 or Win64. No packages need to be installed. The program needs no input. It checks its own results and stops with an error if any of them differs from the output below. It writes to a folder under `%TEMP%` and removes it at the end. ## Expected output The thread id and timestamp differ, and the two drop counts may be 19 or 20: ``` Part 1: four threads, one asynchronous JSON file ok: Flush emptied the queue and ofBlock dropped nothing ok: the callback sink saw one warning per worker ok: the custom sink counted every event by level ok: 2004 JSON lines in workers.jsonl ok: each thread's events stay in order and carry that thread's scope only {"ts":"2026-09-22T01:37:46.320Z","seq":2004,"level":"debug","category":"worker","thread":25760,"scope":"worker 2","msg":"Worker 2 step 500","template":"Worker {worker} step {step}","props":{"worker":"2","step":"500"}} Part 2: a stuck target and the overflow policies ok: ofDropNewest: 19 of 30 events dropped while the target was stuck ok: the rest were written, oldest first ok: ofDropOldest: 19 dropped from the front, the newest event kept ok: a failing target is counted in TargetErrors and never raised All checks passed. ``` Exit code 0 means every check passed; a failed check prints `Exception: Check failed: ...` and sets exit code 1. ## Adapting it Keep Part 1's wiring for a real application: the file sink as `Target`, the asynchronous sink attached to the logger. Choose the overflow policy by what matters more: `ofBlock` (the default) never loses an event but can slow the logging threads when the disk stalls; `ofDropNewest` and `ofDropOldest` never block and report losses in `Dropped`. Call `Flush` (or free the asynchronous sink) before the program ends so the queue reaches the file.