Skip to content

refactor(logs): homogeneous logging with LOG_LEVEL debug mode - #417

Open
MateoLostanlen wants to merge 18 commits into
developfrom
refactor/homogeneous-logging
Open

refactor(logs): homogeneous logging with LOG_LEVEL debug mode#417
MateoLostanlen wants to merge 18 commits into
developfrom
refactor/homogeneous-logging

Conversation

@MateoLostanlen

Copy link
Copy Markdown
Member

Replaces #392, closed by mistake (GitHub cannot reopen a PR once its branch was deleted).

What changes

  • Unify logging setup across pyroengine, pyro_camera_api and pyro-predictor: only the two entrypoints configure logging, both with the same format, and verbosity is driven by a new LOG_LEVEL env var wired through docker-compose. Library modules no longer call logging.basicConfig(force=True) at import. Uvicorn lines are routed through the same formatter too, so every line in both containers shares one format.
  • Cut steady state noise: a quiet inference round and a quiet patrol cycle are each one INFO summary line. Per pose and per frame detail is available at DEBUG. Detections, alerts and failures stay at INFO.
  • Fix log levels: startup retries are warnings instead of errors, unexpected failures keep their traceback via logger.exception, a known down camera is reported once instead of every 30s, and every camera scoped line carries a [cam_id] prefix.
  • Redact credentials from RTSP URLs and ffmpeg commands, which were logged in cleartext at INFO.
  • Replace the log grep engine healthcheck with a heartbeat file, so container liveness no longer depends on log level or message wording.

Examples

Output below was captured by running the same script on develop and on this branch.

One quiet round, 4 poses, nothing detected (every 30s)

Before, 18 lines:

2026-07-29 22:31:58,897 | INFO: Captured image for 192.168.1.10, pose 0
2026-07-29 22:31:58,897 | INFO: Model original pred : [[0.00942827 0.5561888 0.02951318 0.6073602 0.70792276]]
2026-07-29 22:31:58,897 | INFO: pred for 192.168.1.10_0 : [[0.00942827 0.5561888 0.02951318 0.6073602 0.70792276]]
2026-07-29 22:31:58,897 | INFO: Camera '192.168.1.10_0' - No wildfire (confidence: 2.00%)
   ... same 4 lines for poses 1, 2, 3 ...
2026-07-29 22:31:58,898 | INFO: Run focus finder
2026-07-29 22:31:58,898 | INFO: Loop run under 2.34 seconds, sleeping for 27.66 seconds

After, 1 line:

2026-07-29 22:32:06,237 [INFO] pyroengine.core: Inference round: analyzed=4 failed=0 positive=0 max_confidence=0.02 duration=0.0s

The prediction array was printed twice per pose, once by vision.py and once by engine.py. Run focus finder printed on every round even though focus_finder() only acts once an hour.

A real detection stays visible

before: 2026-07-29 22:31:59,919 | INFO: Model original pred : [[0.00942827 0.5561888 0.02951318 0.6073602 0.70792276]]
        2026-07-29 22:31:59,919 | INFO: pred for 192.168.1.10_0 : [[0.00942827 ...]]
        2026-07-29 22:31:59,919 | INFO: Camera '192.168.1.10_0' - Wildfire detected (confidence: 35.40%)

after:  2026-07-29 22:32:06,434 [INFO] pyro_predictor.predictor: [192.168.1.10_0] Wildfire detected (confidence: 35.40%)

Format now carries the logger name

before: 2026-07-29 22:31:58,896 | INFO: Waiting for Pyro Camera API
after:  2026-07-29 22:32:06,236 [INFO] pyroengine.core: Waiting for Pyro Camera API at http://fake

The camera API used a different format in the same deployment. Both now share one format, and engine logs go to stdout explicitly instead of stderr.

Uvicorn kept its own handlers with propagate=False, so its lines had no timestamp at all:

before: INFO:     Started server process [18204]
        INFO:     Application startup complete.
        INFO:     127.0.0.1:65188 - "GET /ping HTTP/1.1" 200 OK

after:  2026-07-30 06:44:39,485 [INFO] uvicorn.error: Started server process [19498]
        2026-07-30 06:44:39,486 [INFO] uvicorn.error: Application startup complete.

The per request access line is now DEBUG only, since the engine polls the capture endpoints every few seconds. At LOG_LEVEL=DEBUG it comes back, in the shared format:

2026-07-30 06:44:48,464 [INFO] uvicorn.access: 127.0.0.1:65251 - "GET /ping HTTP/1.1" 200

Levels

before: | ERROR: API not ready: Connection refused          (normal boot path, every 10s)
after:  [WARNING] pyroengine.core: Camera API not ready yet, retrying in 10s: Connection refused

before: | ERROR: Camera 'x' - unable to upload cache
        | ERROR: ConnectionError(...)                       (two lines, no traceback)
after:  [ERROR] pyroengine.engine: [x] Unable to upload cache: ConnectionError(...)

Patrol cycle, 4 poses every 30s

before: [192.168.1.10] Moving to pose 0         (x4)
        [192.168.1.10] Stored image for pose 0  (x4)
        [192.168.1.10] Returned to pose 0
        [192.168.1.10] Restored manual focus to 520

after:  [192.168.1.10] Patrol cycle: captured=4/4 failed=0 duration=12.3s

Credentials

before: [INFO] start pipeline, rtsp rtsp://admin:S3cret!@192.168.1.10:554/h264, srt ...
after:  [INFO] Start pipeline, rtsp rtsp://***:***@192.168.1.10:554/h264, srt ...

Nothing is lost at LOG_LEVEL=DEBUG

[DEBUG] pyroengine.core: [192.168.1.10_0] Captured image from cam1
[DEBUG] pyroengine.core: [192.168.1.10_1] Captured image from cam1
[DEBUG] pyroengine.core: [192.168.1.10_2] Captured image from cam1
[DEBUG] pyroengine.core: [192.168.1.10_3] Captured image from cam1
[INFO]  pyroengine.core: Inference round: analyzed=4 failed=0 positive=0 max_confidence=0.02 duration=0.0s

Deployment note

The engine healthcheck no longer greps engine.log. It now checks the freshness of the heartbeat file refreshed by the main loop (option --heartbeat-file, default data/heartbeat). On first start the container is unhealthy until the first loop completes, so bump start_period if your rollout treats unhealthy as fatal.

Libraries no longer configure the root logger: basicConfig is called only from
the two entrypoints, with the same format in both services. LOG_LEVEL is now
plumbed through docker-compose and documented in .env.example.
Per-pose captures, raw predictions and no-detection results move to DEBUG.
Each pass now ends with one INFO summary (analyzed/failed/positive/max
confidence/duration). Detections and alerts stay at INFO.

Startup retries are warnings instead of errors, unexpected failures use
logger.exception, and every camera line carries a [cam_id] prefix.
Classifier no longer mutates the module logger level from its constructor.
One INFO line per patrol cycle, per-pose moves and captures move to DEBUG.
The static loop stays silent while healthy and reports recovery instead of
repeating a warning every 30s during a skip window.

ffmpeg stderr, FPS meter, mock no-ops and unsupported Linovision features
drop to DEBUG. RTSP URLs and ffmpeg commands are redacted before logging,
using a shared helper extracted from the RTSP adapter.
Wiring LOG_LEVEL to the engine made the healthcheck grep for strings that are
only emitted at INFO, so LOG_LEVEL=WARNING would mark a healthy engine
unhealthy. The main loop now refreshes data/heartbeat and the healthcheck
checks its freshness instead. The night sleep is chunked so it stays fresh.

Third-party loggers are also clamped to the configured level: pinning them to
WARNING let their warnings through an ERROR root logger.
Refresh the heartbeat per pose and during the end-of-loop sleep so a slow
round or a large --period cannot exceed the healthcheck window. The heartbeat
path is now its own --heartbeat-file option instead of being derived from
--cache, so it stays aligned with the healthcheck.
Uvicorn keeps its own handlers with propagate=False, so its startup and access
lines kept a timestamp-less format of their own. They now go through the root
handler like every other line.
Splitting on the first @ leaked the tail of any password containing one:
rtsp://admin:p@ssword@host became rtsp://***:***@ssword@host. It also mangled
credential-free URLs whose path contains @, dropping the host.

Parse the URL and mask the netloc userinfo instead, and leave anything that is
not a scheme://host URL untouched so the helper stays safe to map over a whole
ffmpeg command line.
Uvicorn's dictConfig pins an explicit INFO level on uvicorn and uvicorn.error,
so its startup lines stayed visible at LOG_LEVEL=ERROR. Reset them to NOTSET so
they inherit the configured level.
verbose was accepted but ignored, so library callers passing verbose=False
started seeing detection and model setup lines. Gate those logs per instance,
downgrading them to DEBUG, which restores the observable behaviour without the
old global logger.setLevel side effect.
One redaction module covers userinfo, credential query params and MediaMTX
streamid forms; a handler filter scrubs relayed lines (ffmpeg stderr) too.
rest.py and url.py delegate to it instead of keeping weaker copies.
Written only on successful capture, night sleep or responsive autofocus, and
deleted at startup, so a blind or crash-looping engine goes unhealthy instead
of keeping a stale file fresh. Healthcheck window 10 min, start_period covers
the cold-boot model download. The summary counts the predictor's ongoing
verdict so hysteresis-kept events no longer read as a quiet round. Drop the
dead tee/engine.log wrapper.
@MateoLostanlen
MateoLostanlen requested a review from fe51 September 8, 2026 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant