Logging
All pyzm internals log to the "pyzm" stdlib logger. How you
configure it depends on whether you are using ZoneMinder or not.
Standalone (ML only, no ZoneMinder)
If you only use pyzm for ML detection (no ZM), configure the "pyzm"
logger with stdlib logging:
import logging
logging.basicConfig(level=logging.DEBUG) # quick & simple
# — or — configure just the pyzm logger:
pyzm_logger = logging.getLogger("pyzm")
pyzm_logger.setLevel(logging.DEBUG)
pyzm_logger.addHandler(logging.StreamHandler()) # print to console
from pyzm import Detector
detector = Detector(models=["yolo11s"])
result = detector.detect("/path/to/image.jpg") # logs appear on console
With ZoneMinder
On a ZoneMinder host, setup_zm_logging() reads zm.conf, the DB
Config table, and environment variables, then writes to ZM’s log file,
database, and syslog using the same format as Perl’s Logger.pm:
from pyzm.log import setup_zm_logging
adapter = setup_zm_logging(name="myapp")
adapter.Info("Hello from pyzm")
adapter.Debug(1, "Verbose detail")
# Override log levels or enable console output
adapter = setup_zm_logging(name="myapp", override={
"dump_console": True,
"log_debug": True,
"log_level_debug": 5,
})
setup_zm_logging() returns a ZMLogAdapter that provides
Debug, Info, Warning, Error, and Fatal methods
matching the legacy pyzm API. All pyzm library internals automatically
share the same log handlers via the "pyzm" logger.
Warning
Fatal() logs at CRITICAL level, calls close() to flush all
handlers, and then calls sys.exit(-1). Only use it for
unrecoverable errors.
Other ZMLogAdapter methods:
close()– close and remove all log handlers from the loggerget_config()– return the resolved config dict (useful for debugging which settings were applied)
How it works
setup_zm_logging() resolves its configuration from up to four sources
and then attaches handlers to the "pyzm" stdlib logger. The precedence
differs slightly by field:
Paths and DB credentials (
logpath,dbuser,dbpassword,dbhost,dbname,webuser,webgroup):override> environment variable >zm.conf/conf.d/*.conf> built-in defaultLog levels and debug flags (
log_level_file,log_level_db,log_level_syslog,log_debug,log_level_debug,log_debug_target,log_debug_file,server_id):override> ZM databaseConfigtable > environment variable > built-in default
The override dict is applied twice – before and after the DB read –
so values you pass there always win.
Where the log file is written
Once the config is resolved, the file path is computed as:
<logpath>/<name>.log
where <logpath> is the resolved directory (see precedence above) and
<name> is the name= you passed to setup_zm_logging() with any
extension stripped. For example, setup_zm_logging(name="zmesdetect_m1")
with logpath=/var/log/zm writes to /var/log/zm/zmesdetect_m1.log.
Special case: when log_debug is on and ZM_LOG_DEBUG_FILE
(log_debug_file) is set to a non-empty path, that exact path is used
instead – useful for redirecting a single component’s debug output to a
dedicated file.
Gate: the file is only opened when log_level_file > 0. With the
default config (level 0 = off), no file is written even if
logpath is set. This level is normally pulled from
ZM_LOG_LEVEL_FILE in the ZM database, so a process that cannot reach
the database silently falls back to “off” and produces no file.
Note
If the resolved logpath directory does not exist,
setup_zm_logging silently skips attaching the file handler (the
underlying OSError from opening the file is caught). Create the
directory before testing custom paths.
Environment variables
All environment variables are optional. They are read first and provide
baseline values that can be overridden by ZM config files, the database,
and the override dict.
Environment variable |
Config key |
Description |
|---|---|---|
|
|
Path to ZM config directory (default |
|
|
ZM database username |
|
|
ZM database password |
|
|
ZM database host ( |
|
|
ZM database name |
|
|
Web user for log file ownership (default |
|
|
Web group for log file ownership (default |
|
|
Log file directory (default |
|
|
Syslog handler level (ZM scale: 1=DBG, 0=INF, -1=WAR, -2=ERR, -5=off) |
|
|
File handler level |
|
|
Database handler level |
|
|
Enable debug logging ( |
|
|
Maximum debug sub-level (1–9) |
|
|
Restrict debug logs to matching process names (pipe-separated) |
|
|
Override log file path when debug is active |
|
|
ZM server ID for database log entries |
|
|
Also print to console ( |
The override dict accepts the same keys listed in the “Config key”
column above (e.g. override={"dump_console": True, "log_debug": 1,
"log_level_debug": 5}). Overrides are applied twice – before and after
the database read – so they always take final precedence.
Up to four handlers are attached to the "pyzm" stdlib logger:
File handler — writes to
/var/log/zm/<name>.log(or the path inZM_LOG_DEBUG_FILE) using ZM’s native format matching Perl’sLogger.pmDatabase handler — writes to ZM’s
Logstable viamysql.connector(columns:TimeKey,Component,ServerId,Pid,Level,Code,Message,File,Line)Syslog handler — sends to syslog facility
LOCAL1Console handler — enabled when
dump_console=True
Each handler’s log level is controlled independently by the corresponding
ZM config (ZM_LOG_LEVEL_FILE, ZM_LOG_LEVEL_DATABASE,
ZM_LOG_LEVEL_SYSLOG).
Signal handlers are registered for log management:
SIGHUP reopens the log file (for log rotation), SIGUSR1/SIGUSR2
increase/decrease verbosity at runtime.
Inspecting and testing the log path
Two helpers let you check paths without standing up the full logger:
from pyzm.log import get_logpath, get_log_file
# The log *directory* (pure config lookup -- no DB, no handlers).
# Precedence: PYZM_LOGPATH > ZM_PATH_LOGS in zm.conf > /var/log/zm.
get_logpath() # e.g. "/var/log/zm"
# The *active* log file path (only meaningful after setup_zm_logging
# has run AND file logging is enabled). Returns None otherwise.
get_log_file() # e.g. "/var/log/zm/myapp.log"
Three ways to test or change the log path:
1. Override at the call site – most explicit, no system changes:
import os
from pyzm.log import setup_zm_logging, get_log_file
os.makedirs("/tmp/zmtest", exist_ok=True)
setup_zm_logging(name="probe", override={
"logpath": "/tmp/zmtest",
"log_level_file": 1, # required -- file logging is off by default
})
print(get_log_file()) # /tmp/zmtest/probe.log
2. Environment variables – no code change, scoped to the shell:
mkdir -p /tmp/zmtest
PYZM_LOGPATH=/tmp/zmtest PYZM_FILELOGLEVEL=1 \
python -c "from pyzm.log import setup_zm_logging, get_log_file; \
setup_zm_logging(name='probe'); print(get_log_file())"
# /tmp/zmtest/probe.log
3. System config – persistent, affects all ZM processes:
Set ZM_PATH_LOGS in /etc/zm/conf.d/01-system-paths.conf (or
zm.conf). This is what ZoneMinder itself reads. Verify what pyzm
sees with:
python -c "from pyzm.log import get_logpath; print(get_logpath())"