Debugging and Logging¶
bac-py uses Python’s standard logging module throughout the stack. Every
module has its own logger under the bac_py namespace, giving you granular
control over what gets logged and at what level.
No extra dependencies are required – logging is built into the Python standard library.
Quick Start¶
Enable logging with a single call before creating a Client:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(name)s %(levelname)s %(message)s",
)
To see detailed protocol-level traces, set DEBUG:
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s %(name)s %(levelname)s %(message)s",
)
You can also target specific modules to avoid noise from the rest of the stack:
import logging
# Only show client-level operations
logging.getLogger("bac_py.app.client").setLevel(logging.DEBUG)
# Only show network-layer routing
logging.getLogger("bac_py.network").setLevel(logging.DEBUG)
# Show everything at WARNING and above by default
logging.basicConfig(level=logging.WARNING)
Logger Hierarchy¶
All loggers are children of the root bac_py logger. Configuring a parent
logger automatically applies to its children. The full hierarchy:
Logger Name |
What It Covers |
|---|---|
|
Application lifecycle (start/stop), APDU dispatch, device info cache |
|
All client request/response methods (56+ log points) |
|
Server handler dispatch, registration, error paths |
|
Transaction state machine: create, complete, timeout, retry |
|
Event algorithm evaluation, state transitions, notifications |
|
COV subscription lifecycle: create, remove, notify |
|
Audit record creation, manager lifecycle |
|
Schedule evaluation, value resolution |
|
Trend sample acquisition, engine lifecycle |
|
NPDU encode/decode, routing field validation |
|
APDU dispatch, network message handling, router cache |
|
Router port start/stop, route table updates, forwarding |
|
Address parsing (IP, Ethernet, IPv6, MS/TP formats) |
|
BACnet/IP unicast/broadcast send, datagram receive |
|
BBMD lifecycle, broadcast forwarding, BDT/FDT queries |
|
Ethernet 802.3 frame send/receive |
|
BACnet/IPv6 VMAC resolution, send/receive |
|
SC connection state machines, hub routing, failover, TLS, BVLC codec |
|
APDU type identification during encode/decode |
|
Tag validation warnings (invalid tag numbers, malformed data) |
|
Vendor-proprietary PropertyIdentifier creation |
|
CHOICE decode warnings (unexpected tag types) |
|
ObjectDatabase add/remove, property read/write |
|
DeviceObject creation |
|
Segment send/receive progress, window management, transfer completion |
|
Serialize/deserialize operations, type errors |
Log Levels¶
bac-py follows standard Python logging levels with consistent semantics:
- DEBUG
Protocol detail and trace information. APDU encode/decode types, individual request/response operations, state machine transitions, segment progress, property reads/writes. High volume – use targeted loggers in production.
- INFO
Lifecycle events and significant operations. Application start/stop, transport start/stop, COV subscriptions created/removed, object database add/remove, event state transitions, segmented transfer completions.
- WARNING
Unusual but recoverable conditions. Unknown objects/properties in server requests, duplicate segments, write-access-denied, tag validation errors, CHOICE decode failures, address parse failures.
- ERROR
Failures that affect operation. Unhandled exceptions in service handlers (includes full traceback via
exc_info=True).
Practical Examples¶
Debugging a failed read¶
If reads are timing out or returning errors, enable client and TSM logging:
import logging
logging.basicConfig(level=logging.WARNING)
logging.getLogger("bac_py.app.client").setLevel(logging.DEBUG)
logging.getLogger("bac_py.app.tsm").setLevel(logging.DEBUG)
This shows the outgoing request, transaction creation, retry attempts, and final timeout or response.
Tracking device discovery¶
To see Who-Is/I-Am traffic and network routing during discovery:
logging.getLogger("bac_py.app.client").setLevel(logging.DEBUG)
logging.getLogger("bac_py.network.layer").setLevel(logging.DEBUG)
logging.getLogger("bac_py.transport.bip").setLevel(logging.DEBUG)
Monitoring server handlers¶
When running a BACnet server, see which requests arrive and any errors:
logging.getLogger("bac_py.app.server").setLevel(logging.DEBUG)
Every incoming service request is logged with its parameters, and every error path logs a WARNING before raising a BACnet error response.
Diagnosing segmentation issues¶
For large transfers that fail mid-stream:
logging.getLogger("bac_py.segmentation.manager").setLevel(logging.DEBUG)
Shows individual segment send/receive, window fills, ACK handling, and duplicate or out-of-window segment warnings.
Tracing SC connections¶
For BACnet Secure Connect debugging:
logging.getLogger("bac_py.transport.sc").setLevel(logging.DEBUG)
Shows WebSocket connect/disconnect, TLS context creation, BVLC message encode/decode, hub routing, failover events, and heartbeat cycles.
Writing Logs to a File¶
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(name)s %(levelname)s %(message)s",
filename="bacnet.log",
)
Or use logging.config.dictConfig() for more advanced setups (multiple
handlers, rotation, structured output):
import logging.config
logging.config.dictConfig({
"version": 1,
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "WARNING",
"formatter": "brief",
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "bacnet.log",
"maxBytes": 10_000_000,
"backupCount": 3,
"level": "DEBUG",
"formatter": "detailed",
},
},
"formatters": {
"brief": {"format": "%(levelname)s %(message)s"},
"detailed": {
"format": "%(asctime)s %(name)s %(levelname)s %(message)s"
},
},
"loggers": {
"bac_py": {"level": "DEBUG", "handlers": ["console", "file"]},
},
})
Performance Notes¶
DEBUG logging on hot paths (encoding, tags, APDU codec) adds overhead from string formatting on every packet. Use DEBUG only on targeted modules during troubleshooting, not globally in production.
INFO logging is suitable for production monitoring. It covers lifecycle events and significant operations without per-packet overhead.
The encoding and tag modules use WARNING-only logging (no DEBUG) specifically to avoid impacting APDU throughput.
See also: Python asyncio debugging for asyncio-specific debug techniques (slow callback detection, etc.).