Skip to main content

Logging

HomeCore uses tracing for structured logging. Three independent outputs can run simultaneously, each with its own format and level filter.

Outputs at a glance​

OutputDefaultBest for
stderrenabled, pretty formatDevelopment, systemd journal
filedisabledProduction, post-mortem analysis, log aggregators
syslogdisabledCentralised aggregation (Graylog, Loki, rsyslog)

Quick level override​

The fastest way to change log verbosity is the RUST_LOG environment variable. It always takes highest precedence over config:

# Default — info from all crates
cargo run -p homecore

# Debug from the rule engine only
RUST_LOG=info,hc_core=debug cargo run -p homecore

# Debug from rule engine + MQTT client
RUST_LOG=info,hc_core=debug,hc_mqtt_client=debug cargo run -p homecore

# Deep dive — rule evaluation loop + action executor
RUST_LOG=info,hc_core::engine=debug,hc_core::executor=debug cargo run -p homecore

# Everything (very noisy — broker internals included)
RUST_LOG=trace cargo run -p homecore

# Silence all but errors
RUST_LOG=error cargo run -p homecore

Configuration reference​

All settings live under [logging] in config/homecore.toml.

[logging] — global defaults​

[logging]
# Global default level for all crates
# error | warn | info | debug | trace
level = "info"

# Timestamp timezone
# "local" — local system time with UTC offset (default)
# "utc" — UTC with Z suffix
time_display = "local"

[logging.targets] — per-crate overrides​

[logging.targets]
hc_core = "debug" # rule engine, scheduler, state bridge, action executor
hc_api = "info" # HTTP/WebSocket handlers
hc_auth = "warn" # JWT, password hashing
hc_state = "info" # device registry, history
hc_mqtt_client = "debug" # MQTT connection, topic routing
hc_broker = "warn" # embedded broker (noisy at debug)
hc_topic_map = "debug" # ecosystem profile matching
hc_notify = "info" # notification channels
hc_scripting = "warn" # Rhai script execution

Sub-module granularity:

[logging.targets]
"hc_core::engine" = "debug" # rule evaluation loop only
"hc_core::executor" = "debug" # action execution only
"hc_core::bridge" = "debug" # MQTT→EventBus state bridge only

[logging.stderr] — console output​

[logging.stderr]
enabled = true
format = "pretty" # "pretty" | "compact" | "json"
ansi = true # set false for systemd journal / Docker

[logging.file] — rolling log file​

[logging.file]
enabled = false
dir = "logs" # created automatically
prefix = "homecore"
rotation = "daily" # "daily" | "hourly" | "weekly" | "never"
max_size_mb = 100 # rotate when file exceeds this (0 = size-only rotation off)
compress = true # gzip rotated files immediately after rotation
prune_after_days = 5 # delete rotated logs older than N days (0 = never prune)
format = "json" # "json" | "compact" | "pretty"

Active file is always <prefix>.log (never compressed). Rotated files follow:

  • Daily: homecore.2026-03-27.log.gz
  • Hourly: homecore.2026-03-27_14.log.gz

[logging.syslog] — remote syslog​

[logging.syslog]
enabled = false
transport = "udp" # "udp" | "tcp"
host = "192.168.1.100"
port = 514
protocol = "rfc5424" # "rfc5424" | "rfc3164"
facility = "daemon"
app_name = "homecore"
level = "warn" # optional override; defaults to global [logging].level

Common recipes​

Development — verbose rule engine, quiet broker​

[logging]
level = "info"
time_display = "local"

[logging.targets]
hc_core = "debug"
hc_mqtt_client = "debug"
hc_broker = "warn"

[logging.stderr]
enabled = true
format = "pretty"
ansi = true

Production — structured file + remote warnings​

[logging]
level = "info"

[logging.stderr]
enabled = false # no console when running as a service

[logging.file]
enabled = true
dir = "/var/log/homecore"
rotation = "daily"
format = "json"
compress = true
prune_after_days = 7 # auto-delete logs older than 7 days

[logging.syslog]
enabled = true
transport = "udp"
host = "192.168.1.50"
port = 514
level = "warn" # only warnings and above go to remote

systemd — journal-friendly​

[logging.stderr]
enabled = true
format = "compact"
ansi = false # no ANSI codes in journal

Grafana Loki / Graylog via file​

[logging.file]
enabled = true
dir = "/var/log/homecore"
format = "json" # structured fields are required for parsing
rotation = "hourly"

Log target reference​

CrateTarget nameWhat it covers
hc-corehc_coreRule engine, scheduler, state bridge, executor, timer/switch/mode managers
hc-apihc_apiHTTP handlers, WebSocket stream, auth middleware
hc-authhc_authJWT issuance/validation, password hashing
hc-statehc_stateDevice registry (redb), time-series history (SQLite)
hc-mqtt-clienthc_mqtt_clientMQTT connection, subscriptions, topic routing
hc-brokerhc_brokerEmbedded rumqttd broker internals
hc-topic-maphc_topic_mapEcosystem profile matching, payload transforms
hc-notifyhc_notifyEmail, Pushover, Telegram notification channels
hc-scriptinghc_scriptingRhai script execution

Log level API​

Change log levels at runtime without restart (Admin required):

# Temporarily enable debug for rule engine
curl -s -X PUT http://localhost:8080/api/v1/system/log-level \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"target":"hc_core","level":"debug"}'

# Read the current effective log levels
curl -s http://localhost:8080/api/v1/system/log-level \
-H "Authorization: Bearer $TOKEN" | jq

Live log stream (WebSocket)​

# Tail all logs at info+
websocat "ws://localhost:8080/api/v1/logs/stream?token=$TOKEN"

# Filter to warn and above
websocat "ws://localhost:8080/api/v1/logs/stream?token=$TOKEN&level=warn"

# Filter to a specific module
websocat "ws://localhost:8080/api/v1/logs/stream?token=$TOKEN&module=hc_core"

The log stream replays the last 100 buffered log lines on connect, then streams new lines in real time.

Plugin logging​

Each hc-* plugin manages its own log file and rotation independently of the homeCore server. All plugins share the same [logging] config schema, configured in the plugin's own config/config.toml:

[logging]
level = "info" # stderr log level; RUST_LOG overrides this
rotation = "daily" # daily | hourly | weekly | never
max_size_mb = 100 # rotate when file exceeds this MB (0 = time-only)
compress = true # gzip rotated files in a background thread
prune_after_days = 5 # delete rotated logs older than N days (0 = never)
log_forward_level = "info" # min level forwarded to core over MQTT
OptionDefaultDescription
level"info"Stderr log level when RUST_LOG is not set. Accepts any tracing filter directive, e.g. "debug" or "hc_hue=debug,rumqttc=warn".
rotation"daily"Time-based rotation strategy: daily, hourly, weekly, or never.
max_size_mb100Size-based rotation threshold. Combined with rotation as "whichever comes first". Set 0 to disable size-based rotation.
compresstrueGzip-compress rotated files in a background thread immediately after rotation. The active log file is never compressed.
prune_after_days0Delete rotated .log and .log.gz files older than this many days. Runs at startup and after each rotation. 0 disables pruning.
log_forward_level"info"Minimum log level forwarded to the HomeCore broker over MQTT. Plugin logs at or above this level appear in the admin UI's Activity page and the /api/v1/logs/stream WebSocket. Set to "off" to disable forwarding entirely.

MQTT log forwarding​

Plugins forward their tracing logs to HomeCore over MQTT, making them visible in the admin UI alongside core logs. The forwarding uses QoS 0 (fire-and-forget) to avoid stalling the logging pipeline.

Topic: homecore/plugins/{plugin_id}/logs

Payload format (same as the core's LogLine):

{
"timestamp": "2026-04-07T12:00:00Z",
"level": "INFO",
"target": "hc_yolink::bridge",
"message": "Device registered",
"fields": {"device_id": "yolink_front_door"}
}

The core's state bridge receives these messages and injects them into the log stream broadcast channel, so they appear in:

  • The /api/v1/logs/stream WebSocket (live tail)
  • The admin UI Activity page (filtered by source = "log")

Plugin log files follow the same naming convention as the server:

FileDescription
logs/hc-{plugin}.logActive log (always uncompressed)
logs/hc-{plugin}.2026-03-27.log.gzRotated daily file (compressed)
logs/hc-{plugin}.2026-03-27_14.log.gzRotated hourly file (compressed)
logs/hc-{plugin}.2026-03-27.1.log.gzSecond size-triggered rotation in the same period

Defaults preserve the previous behavior if the [logging] section is absent from a plugin config.