Logging¶
The platform uses ACE's ACE_LOG_MSG infrastructure with a thin wrapper, Utils::setupLogging(name), that every role calls during startup and that every worker thread calls before it begins work. Per-thread log files, JSON output, and trace-level call entry/exit are all toggled from the active TOML [logging] block.
Log Levels¶
ACE_LOG_MSG predefines log priorities used throughout the codebase:
| Macro | Priority | Used for |
|---|---|---|
LM_DEBUG |
DEBUG | Routine state changes, lifecycle events. |
LM_INFO |
INFO | Successful business outcomes (CCA sent, CDR rated, bill formatted). |
LM_NOTICE |
NOTICE | Rarely used. |
LM_WARNING |
WARNING | Recoverable misconfiguration (e.g. TLS fallback to insecure when allowed). |
LM_ERROR |
ERROR | Per-event failures that do not terminate the role. |
LM_CRITICAL |
CRITICAL | Failures that abort the current operation; license / DB connect failures. |
LM_SHUTDOWN |
SHUTDOWN | Used in main.cpp to mark fatal startup paths. |
Every emitter prefixes the message with [%t | %T] so each line includes the ACE thread id and a timestamp, even when JSON formatting is disabled.
Configuration¶
Each TOML config carries:
[logging]
to_file = 0 # 0 → STDERR, 1 → per-process log file
json_format = 0 # 0 → ACE text format, 1 → JSON
pretty_format = 0 # 1 → indented JSON (only with json_format=1)
trace = 0 # 1 → ACE_Trace::start_tracing()
log_path = "/.../log" # directory for log files when to_file=1
Utils::setupLogging(name) (in src/utils/utility.cpp) interprets these:
- If
to_file=1, anofstreamis opened atlog_path/<name>_<unix_timestamp>.log, registered as the ACE message stream, and STDERR is disabled. - If
json_format=1, aJsonLogCallbackis wired up viaACE_LOG_MSG->msg_callback()and the output mode is switched toMSG_CALLBACK. Pretty-printed output is enabled whenpretty_format=1. - If
trace=1,ACE_Trace::start_tracing()is enabled. EveryACE_Trace aceTrace("Method::name")declaration in the codebase emits a TRACE line on entry and on exit. With tracing disabled, the declarations are essentially no-ops.
ensureThreadLogging(name) is a thread_local guard that calls setupLogging(name) exactly once per thread — used by the thread-pool workers so each worker initialises its logging setup the first time it runs a task.
JSON Format¶
include/utils/json_logging.hpp defines JsonLogCallback (an ACE_Log_Msg_Callback). Each log record is rendered as one JSON object per line:
{
"timestamp":1735689600.123456,
"priority":"INFO",
"pid":12345,
"thread_id":"0x16fb8b000",
"file":"acceptor.cpp",
"line":290,
"message":"[1 | 2026-01-01 12:00:00.123456] Sent CCA (96 bytes). Result-Code: 2001 [Success]"
}
The callback writes to std::cerr; combined with to_file=1, the JSON lines land in the role's log file and can be piped to standard log shippers (Filebeat, Vector) for downstream indexing.
Per-Role Log Files¶
When the platform is started with scripts/run.sh all, each role's stdout and stderr are redirected to its own nohup log under log/. Combined with to_file=1, this gives two files per role: the redirected nohup log (process-level) and the ACE-managed in-process log.
log/
├── billing.1.server.log # nohup output for billing role 1
├── billing.2.server.log
├── billing.3.server.log
├── billing.4.server.log
├── cdr_loader.server.log
├── prepaid.server.log
├── crm.server.log
├── stats.server.log
├── bill_formatter.server.log
├── crm_<unix_ts>.log # ACE in-process log for the CRM role
├── crm_grpc_thread_<ts>.log # ACE in-process log for the gRPC worker thread
├── main_<unix_ts>.log # ACE in-process log for main()
├── prepaid_mass_rating.server.log
└── diameter_client.log
The <name> parameter passed to Utils::setupLogging shapes the in-process filename: main for main.cpp, prepaid for the DIAMETER worker thread, crm for the CRM gRPC handler, cdr_loader for the loader worker, bill_formatter_<bill_id> for each bill render, billing_activity_<id> for each billing activity worker.
Tracing¶
ACE_Trace aceTrace("Method::name") is declared at the top of nearly every method. With trace=1 enabled, ACE emits TRACE-level entries on entry and exit, indented to reflect call depth:
(123|2026-01-01 12:00:00.123) calling PrepaidServer::svc in file `acceptor.cpp' on line 77
(123|2026-01-01 12:00:00.124) calling Subscriber::Subscriber in file `subscriber.cpp' on line 12
(123|2026-01-01 12:00:00.125) leaving Subscriber::Subscriber
(123|2026-01-01 12:00:00.130) leaving PrepaidServer::svc
Tracing is verbose and intended for development — leave trace=0 in production. Note that the Refdata getPP/getSV/etc. accessors are themselves ACE_Trace-wrapped, so enabling tracing under load amplifies log volume substantially.
Notification Channel¶
In addition to log files, BillingHandler, BillFormatterHandler, and the CRM use the notifications table as a structured operator-facing channel (see include/notification.hpp). Notifications surface in the CRM dashboard with status and a deep-link URL; logs remain the source of truth for engineers, while notifications are the operator-facing summary.
Reading Specific Roles¶
# Tail the live billing role for cycle 1
tail -F log/billing.1.server.log
# Find the most recent in-process log for the CRM role
ls -1t log/crm_*.log | head -1
scripts/run.sh <role> (without all) runs the role in the foreground and tails its log automatically.