Skip to content

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, an ofstream is opened at log_path/<name>_<unix_timestamp>.log, registered as the ACE message stream, and STDERR is disabled.
  • If json_format=1, a JsonLogCallback is wired up via ACE_LOG_MSG->msg_callback() and the output mode is switched to MSG_CALLBACK. Pretty-printed output is enabled when pretty_format=1.
  • If trace=1, ACE_Trace::start_tracing() is enabled. Every ACE_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.