Logging¶
Cosalette produces structured JSON logs for production and human-readable text logs for development, with UTC timestamps, correlation metadata, and zero external dependencies.
Two Formats¶
The format is selected via logging.format in settings or --log-format
on the CLI:
NDJSON Format¶
JSON logs follow the NDJSON (Newline Delimited JSON) convention: one
complete JSON object per line, no embedded newlines. This is critical for
container log drivers (Docker, Kubernetes) that split on \n.
def format(self, record: logging.LogRecord) -> str:
entry = {
"timestamp": datetime.fromtimestamp(record.created, tz=UTC).isoformat(),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
"service": self._service,
}
if self._version:
entry["version"] = self._version
if record.exc_info and record.exc_info[0] is not None:
entry["exception"] = self.formatException(record.exc_info)
if record.stack_info:
entry["stack_info"] = self.formatStack(record.stack_info)
return json.dumps(entry, default=str) # (1)!
json.dumpswithdefault=strensures even unexpected types (e.g.pathlib.Path) serialise without raising. Tracebacks are escaped byjson.dumps, so multi-line exceptions become single-line JSON.
UTC Timestamps¶
All timestamps are UTC in RFC 3339 / ISO 8601 format:
JSON Fields¶
| Field | Type | Always present | Description |
|---|---|---|---|
timestamp |
string | Yes | ISO 8601 UTC timestamp |
level |
string | Yes | Python log level (INFO, WARNING, etc.) |
logger |
string | Yes | Dotted logger name (cosalette._app) |
message |
string | Yes | Formatted log message |
service |
string | Yes | Application name (for log correlation) |
version |
string | When non-empty | Application version |
exception |
string | When present | Formatted traceback |
stack_info |
string | When present | Stack trace (if stack_info=True) |
Correlation Metadata¶
Every log line includes service and version, enabling log aggregators to
filter and group entries without extra parser configuration:
# Loki query: all errors from velux2mqtt
{service="velux2mqtt"} |= "ERROR"
# Elasticsearch query: specific version
{"query": {"match": {"version": "0.3.0"}}}
Custom Formatter Over python-json-logger¶
Cosalette implements its own JsonFormatter (~70 lines) rather than depending
on python-json-logger:
| Consideration | Custom formatter | python-json-logger |
|---|---|---|
| Dependencies | Zero (stdlib only) | One additional package |
| Field control | Full — matches project schema | Library defaults, overridable |
| Container image | Smaller | Extra install step |
| Maintenance | Owned by project | Third-party release cycle |
configure_logging()¶
The configure_logging() function is called once during Phase 1 (Bootstrap):
It performs these steps:
- Clear existing handlers on the root logger (prevents duplicate output)
- Build formatter —
JsonFormatterfor"json",logging.Formatterfor"text" - StreamHandler →
stderr(always installed) - RotatingFileHandler → optional, when
settings.logging.fileis set
Text Format String¶
Produces output like:
RotatingFileHandler¶
When logging.file is configured, logs are also written to a rotating file:
| Parameter | Value |
|---|---|
maxBytes |
settings.logging.max_file_size_mb (default: 10 MB) |
backupCount |
settings.logging.backup_count (default: 3) |
encoding |
UTF-8 |
The file handler uses the same formatter as the stream handler (JSON or text), so file logs and stderr logs are always in the same format.
Log Output Examples¶
{"timestamp": "2026-02-14T12:34:57.123456+00:00", "level": "ERROR", "logger": "cosalette._app", "message": "Device 'blind' crashed: Connection refused", "service": "velux2mqtt", "version": "0.3.0", "exception": "Traceback (most recent call last):\n File \"_app.py\", line 268\n ...\nConnectionRefusedError: Connection refused"}
Configuration Reference¶
LOGGING__LEVEL=DEBUG # DEBUG, INFO, WARNING, ERROR, CRITICAL
LOGGING__FORMAT=json # json or text
LOGGING__FILE=/var/log/myapp/myapp.log # optional
LOGGING__BACKUP_COUNT=5 # rotated file generations
Or via CLI override:
CLI flags override environment variables and .env settings, following the
configuration hierarchy.
See Also¶
- Configuration — LoggingSettings and the configuration hierarchy
- Error Handling — errors are logged at WARNING + published to MQTT
- Lifecycle — logging is configured in Phase 1 (Bootstrap)
- ADR-004 — Logging Strategy