Py/Logging/How To/Observe uvicorn and FastAPI app logging to verify JSON logs
Properties
How To Observe uvicorn and FastAPI app Logging to verify JSON logs
- Below are a few proven ways an agent can "watch" the JSON that the service writes to the terminal when one runs a mise/Task like
mise run serveto bring up a FastAPI application. 1 Spin the server up in a subprocess and stream its STDOUT/STDERR
mise run serveis just a shell wrapper arounduvicorn fastapi_example.main:app --reload --host 0.0.0.0 --port 8000(as defined inmise.toml) repomix-output.- Your agent can launch that command exactly once and read the pipes line-by-line:
import subprocess, json, textwrap, os, signal, sys, time # Make sure logs flush immediately env = dict(os.environ, PYTHONUNBUFFERED="1") proc = subprocess.Popen( ["mise", "run", "serve"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, # merge the two streams text=True, # decode bytes → str bufsize=1, # line-buffered env=env, ) try: for raw in proc.stdout: # blocks until newline line = raw.rstrip() # Skip uvicorn banner lines; parse only JSON if line.startswith("{") and line.endswith("}"): event = json.loads(line) print(f"🔍 observed event → {event['event']}") finally: proc.send_signal(signal.SIGINT) # graceful ^C proc.wait(timeout=10)Tips / foot-guns
- This is the simplest approach and works in exactly the same way your pytest assertions capture logs with
capsysintests/unit/test_main.py. repomix-output
2 Run Uvicorn in-process and attach a custom log handler
- If your agent is written in Python and you'd like tighter control, import the FastAPI app and start Uvicorn programmatically:
import asyncio, uvicorn, logging, json from fastapi_example.main import app class CaptureHandler(logging.Handler): def emit(self, record): try: event = json.loads(record.getMessage()) # do something with event dict here… except json.JSONDecodeError: pass # ignore banner lines logging.getLogger().addHandler(CaptureHandler()) config = uvicorn.Config(app, host="0.0.0.0", port=8000, log_config=None) server = uvicorn.Server(config) asyncio.run(server.serve())- Because you start Uvicorn yourself, there is no reloader process, so every log comes through one handler.
- You can shut the server down cleanly by calling
await server.shutdown()or cancelling the asyncio task.
3 Create a dedicated
serve-jsontask inmise.toml- Sometimes you only need JSON logs (no reloader, no banner) while the agent is running. Add a second task:
[tasks.serve-json] description = "Run FastAPI server without reload, JSON only" run = "uvicorn fastapi_example.main:app --host 0.0.0.0 --port 8000"- Run it with
mise run serve-jsonfrom the same subprocess wrapper shown in § 1.
4 Use the capsys strategy as a pattern
- In unit tests one can capture structured logs in-memory and parse them in capsys assertions.
- If the agent's job is only to verify "all logs are JSON," you may not need to boot the whole server—just import the app, make a request with
httpx.AsyncClient, and read what lands incapsys/caplog. That avoids any IPC entirely.
Choosing the right technique
- Below are a few proven ways an agent can "watch" the JSON that the service writes to the terminal when one runs a mise/Task like