Logging
GGCommons configures your process’s logging framework for you from the logging section of your
component config. You get a sensible root level, a console/stdout sink that is always on, an optional
size-rotated file sink, per-logger level overrides, and a structured one-JSON-object-per-line sink
that is the default on Kubernetes. Your component then logs through its language’s normal facility —
Log4j2/SLF4J, the stdlib logging module, tracing, or the exported TypeScript logger.
There is no gg.logging() accessor. Logging is configured by the framework when you build your
GGCommons instance; component code just obtains a logger and writes to it.
Where logging is configured
Section titled “Where logging is configured”Logging is driven by the logging section of the config delivered through your -c/--config source
(see the Configuration guide). The framework applies it automatically at
startup and re-applies it on hot reload — you never call the configurator yourself.
A minimal logging section looks like this (the format token is the one line that differs per SDK):
{ "logging": { "level": "INFO", "java_format": "%d{yyyy-MM-dd HH:mm:ss} [%-5p] %c{1}: %m%n" }}{ "logging": { "level": "INFO", "python_format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s" }}{ "logging": { "level": "INFO", "rust_format": "{timestamp} {level} {target}: {message}" }}{ "logging": { "level": "INFO", "ts_format": "{timestamp} [{level}] {message}" }}If you omit the format token entirely, each SDK falls back to its own built-in default (shown in
Format tokens below) — for Java, Python, and TypeScript that is a default pattern,
while Rust uses the tracing-subscriber built-in fmt layer rather than a token template — unless
the active platform supplies a default; on Kubernetes that default is the json sink.
Levels
Section titled “Levels”level is the root logger level. The schema enum is TRACE | DEBUG | INFO | WARN | ERROR | FATAL
and defaults to INFO.
The enum is shared, but the levels are not honored uniformly — each language maps the config
string onto its own logging framework, and the extremes (TRACE, FATAL) behave differently:
| Config value | Java | Python | Rust | TypeScript |
|---|---|---|---|---|
TRACE |
honored | folds to INFO (no TRACE level) |
honored | folds to DEBUG |
DEBUG |
honored | honored | honored | honored |
INFO |
honored | honored | honored | honored |
WARN |
honored | honored (WARNING) |
honored | honored |
ERROR |
honored | honored | honored | honored |
FATAL |
honored | honored (CRITICAL) |
silences output — avoid | folds to INFO |
For the broadest cross-language consistency, stick to DEBUG, INFO, WARN, and ERROR.
Per-logger levels
Section titled “Per-logger levels”loggers maps a logger name to a level, letting you turn one noisy component up or down without
changing the root level. Keys must match ^[a-zA-Z0-9._-]+$; values are level strings from the same
enum. The keys and values are identical across all four SDKs:
{ "logging": { "level": "INFO", "loggers": { "com.example.MyApp": "DEBUG", "app.db": "WARN" } }}How the override is matched differs by SDK, but the config shape is the same:
- Java — a Log4j2
LoggerConfigper name (withadditivity=false). - Python —
logging.getLogger(name).setLevel(...)per name. - Rust —
target=leveldirectives added to thetracingEnvFilter. - TypeScript — longest dotted-prefix match of the logger name against the
loggerskeys.
Format tokens
Section titled “Format tokens”Each SDK formats text log lines using its own format token, written in its own syntax. A token from one language is meaningless to the others, and each library reads only its own key.
java_format is a Log4j2 PatternLayout.
{ "logging": { "java_format": "%d{yyyy-MM-dd HH:mm:ss} [%-5p] %-25.25c{1}(%4L): %m%n" } }Common conversion specifiers: %d{...} timestamp, %-5p level, %c{1} logger (last segment),
%L line number, %t thread, %m message, %n newline. Default when unset:
%d{yyyy-MM-dd HH:mm:ss} [%-5p] %-25.25c{1}(%4L): %m%n.
python_format is a logging.Formatter format string.
{ "logging": { "python_format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s" } }Common fields: %(asctime)s timestamp, %(levelname)s level, %(name)s logger,
%(message)s message. Default when unset:
%(asctime)s [%(levelname)s] %(name)s: %(message)s.
rust_format is a token template. Supported tokens: {timestamp}, {level}, {target},
{message}.
{ "logging": { "rust_format": "{timestamp} {level} {target}: {message}" } }Note Rust uses {target} (not {logger}). Unlike the other SDKs, Rust has no built-in
token-template default: when rust_format is omitted (and the active platform supplies no json
default), Rust falls back to the tracing-subscriber built-in fmt layer (e.g.
2024-06-27T12:00:00.123456Z INFO target: message — note the colon after the target), not a token
template. The {timestamp}, {level}, {target}, and {message} tokens apply only when
rust_format is explicitly set to a non-json template.
ts_format is a token template. Supported tokens: {timestamp}, {level}, {logger},
{message}.
{ "logging": { "ts_format": "{timestamp} [{level}] {message}" } }Note TypeScript uses {logger} (not {target}). Default when unset:
{timestamp} [{level}] {message}.
Setting the format token to the literal value json (case-insensitive) switches that SDK to the
stdout-JSON sink instead of a text layout.
Console and file logging
Section titled “Console and file logging”The console/stdout sink is always on — every SDK installs it unconditionally. To also write to a
size-rotated file, add a fileLogging object. These keys are the same in all four SDKs:
{ "logging": { "level": "INFO", "fileLogging": { "enabled": true, "filePath": "/var/log/{ComponentName}.log", "maxFileSize": "10MB", "backupCount": 5 } }}enabled(required whenfileLoggingis present, boolean, defaultfalse) — turns the file sink on.filePath(string) — the active log file. Supports template variables such as{ComponentName}and{ThingName}(see the Configuration guide).maxFileSize(string, default"10MB") — rotate when the file reaches this size. Accepts units like512KB,10MB,1GB.backupCount(integer>= 0, default5) — how many rotated backups to keep.
When enabled is false (or fileLogging is omitted), only the console sink runs.
The stdout-JSON sink
Section titled “The stdout-JSON sink”For machine-readable logs (log aggregators, Kubernetes), GGCommons can emit one JSON object per
line to stdout instead of a text layout. Each line carries a stable field set: timestamp
(ISO-8601, UTC Z), level, logger, message, any non-empty correlation fields
(thing, pod, namespace, node), and an exception field when one is present.
The JSON sink is selected by the effective format, resolved with this precedence:
-
An explicit
*_formattoken set tojson(case-insensitive). -
Otherwise, the platform-profile default — which is
jsonon--platform KUBERNETESand unset on GREENGRASS / HOST. -
Otherwise, the SDK’s built-in text default.
So on Kubernetes you get JSON logs automatically. On any platform you can force it by setting your
language’s format token to json:
{ "logging": { "level": "INFO", "java_format": "json" } }{ "logging": { "level": "INFO", "python_format": "json" } }{ "logging": { "level": "INFO", "rust_format": "json" } }{ "logging": { "level": "INFO", "ts_format": "json" } }A few per-language details to know about the JSON field set:
- Java / Python / TypeScript put exceptions under a
thrownfield, present only when an exception/error is supplied. Python additionally emits astackfield and any callerextra=fields. - Rust preserves arbitrary structured event fields verbatim (e.g. a logged
errorfield) rather than a fixedthrownkey.
Getting a logger and emitting logs
Section titled “Getting a logger and emitting logs”Each SDK exposes logging through its native facility. The library has already configured the root logger by the time your component code runs, so you just get a logger and write to it.
import com.mbreissi.ggcommons.logging.Logger;import com.mbreissi.ggcommons.logging.LoggerFactory;
Logger log = LoggerFactory.getLogger(MyApp.class); // or LoggerFactory.getLogger("my.logger")
log.info("publish interval set to {}ms", interval); // SLF4J-style {} placeholderslog.warn("retrying after error", throwable); // (String, Throwable) overloadif (log.isDebugEnabled()) { log.debug("payload bytes: {}", payload.length);}LoggerFactory.getLogger(Class<?>) and getLogger(String) return a thin Logger facade
(trace/debug/info/warn/error plus is*Enabled() predicates) that auto-detects the
underlying framework (SLF4J ▸ Log4j2 ▸ JUL). Components may also use Log4j2 directly
(LogManager.getLogger(...)) — the library has already reconfigured the Log4j2 root logger.
import logging
log = logging.getLogger(__name__)
log.info("publish interval set to %sms", interval)log.warning("retrying after error", exc_info=True)log.debug("payload bytes: %d", len(payload))There is no GGCommons logger factory in Python — the library configures the root logger, so the
stdlib logging.getLogger(name) is all you need. Per-logger levels from logging.loggers apply by
name.
use tracing::{debug, info, warn};
info!(publish_interval = interval, "configuration changed");warn!(error = %err, "retrying after error");debug!(payload_bytes = payload.len(), "received payload");Components log via the tracing macros — there is no getLogger. The library installs the global
tracing-subscriber once during build; structured key/value fields (e.g. publish_interval = ...)
are preserved verbatim in the JSON sink.
import { logger, getLogger } from "ggcommons";
logger.info("publish interval set to " + interval + "ms");
const dbLog = getLogger("app.db");dbLog.debug("db detail"); // honors logging.loggers["app.db"]dbLog.error("query failed", err); // (message, error?) signaturelogger is the process-wide root logger ("ggcommons"); getLogger(name) returns a named,
hierarchically-leveled, cached Logger with debug/info/warn/error methods. There is no
trace/fatal method (TRACE folds to DEBUG, FATAL to INFO).
Early-logging bootstrap
Section titled “Early-logging bootstrap”A few startup facts — “platform resolved …”, “messaging connected …” — are determined before the
logging config has been applied, so they cannot honor your configured level or format on first
emission. To avoid losing them, GGCommons defers and re-emits those startup lines after
config-driven logging is initialized, so they appear with your chosen level, format, and sink (text
or JSON) like everything else. This is automatic in all four SDKs; you do not need to do anything.
The practical effect is that your very first configured log lines include the platform/transport
bootstrap facts, formatted consistently with the rest of your output.
globalControl
Section titled “globalControl”globalControl (boolean, default false) is only meaningful on Java, where true switches to a
manager that rebuilds the entire Log4j2 configuration (every configured logger gets its own
appenders) instead of just the root and per-logger configs. Python parses the flag but never acts on
it, and Rust/TypeScript have no equivalent — those SDKs always rebuild/replace the root logger
regardless. Leave it false unless you specifically need Java’s whole-config rebuild.
Hot reload
Section titled “Hot reload”When your config source supports hot reload, a change to logging is re-applied live. Java, Python,
and TypeScript re-run the full configurator (level, format, and file sink all re-applied; Python and
TypeScript can swap the file writer live). Rust can only swap the level live — its format and
file layers are fixed at startup, so changing rust_format or fileLogging on Rust requires a
restart.