Logging API
This page is the API reference for EdgeCommons logging: the logger type in each SDK, how you obtain one, the log methods and level predicates, the structured UNS log publisher, the configuration type, the configurator entry points, and the per-language format tokens. For the concepts behind these — how levels are honored, the stdout-JSON sink, file rotation, log-bus publishing, and hot reload — see the Logging guide.
There is no gg.logging() accessor in any SDK. The framework configures your language’s logging
framework when you build your EdgeCommons instance; component code obtains a logger from the native
facility and writes to it. The optional structured bus publisher is exposed separately as
getLogs() in Java and logs() in Python, Rust, and TypeScript.
At a glance
Section titled “At a glance”| SDK | Obtain a logger | Logger type | Log methods | Predicates | Format key |
|---|---|---|---|---|---|
| Java | LoggerFactory.getLogger(...) |
com.mbreissi.edgecommons.logging.Logger (interface) |
trace debug info warn error |
isTraceEnabled() … isErrorEnabled() |
java_format |
| Python | stdlib logging.getLogger(name) |
stdlib logging.Logger |
debug info warning error critical |
isEnabledFor(level) |
python_format |
| Rust | tracing macros (no factory) |
n/a (macros) | trace! debug! info! warn! error! |
tracing level checks |
rust_format |
| TypeScript | logger / getLogger(name) |
Logger (class) |
debug info warn error |
none exported | ts_format |
Obtaining a logger
Section titled “Obtaining a logger”import com.mbreissi.edgecommons.logging.Logger;import com.mbreissi.edgecommons.logging.LoggerFactory;
Logger byClass = LoggerFactory.getLogger(MyApp.class);Logger byName = LoggerFactory.getLogger("com.example.MyApp");LoggerFactory is a static factory:
public final class LoggerFactory { public static Logger getLogger(Class<?> clazz); public static Logger getLogger(String name);}The factory auto-detects the underlying framework once at class-load time, preferring SLF4J, then
Log4j2, then JUL. You can also bypass the facade and use Log4j2 directly
(LogManager.getLogger(...)) — the library has already reconfigured the Log4j2 root logger.
import logging
log = logging.getLogger(__name__)There is no EdgeCommons 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
the name you pass here.
use tracing::{debug, info, warn, error};There is no getLogger in Rust. Components emit through the tracing macros; the library installs
the global tracing-subscriber once during build(). The macro’s target defaults to the current
module path, which is what per-logger loggers directives match against.
import { logger, getLogger } from "@edgecommons/edgecommons";
logger.info("using the process-wide root logger"); // name "edgecommons"
const dbLog = getLogger("app.db"); // named, cached, hierarchically leveledlogger is the process-wide root Logger (named "edgecommons"). getLogger(name) returns a named,
cached Logger whose level is resolved by longest dotted-prefix match against logging.loggers.
The logger type
Section titled “The logger type”com.mbreissi.edgecommons.logging.Logger is an interface. Each of trace/debug/info/warn/error
has three overloads, plus a level predicate per level:
public interface Logger { void trace(String message); void trace(String message, Object... args); // SLF4J-style {} placeholders void trace(String message, Throwable throwable); // ... identical trio for debug, info, warn, error ...
boolean isTraceEnabled(); boolean isDebugEnabled(); boolean isInfoEnabled(); boolean isWarnEnabled(); boolean isErrorEnabled();}The (String, Object...) overload uses SLF4J-style {} placeholders; the (String, Throwable)
overload attaches an exception. There is no fatal() method on this interface.
Python uses the standard-library logging.Logger returned by logging.getLogger(name) — EdgeCommons
does not define its own logger type. The standard methods are:
log.debug("payload bytes: %d", len(payload))log.info("publish interval set to %sms", interval)log.warning("retrying after error", exc_info=True)log.error("query failed")log.critical("fatal condition") # FATAL maps to CRITICAL
if log.isEnabledFor(logging.DEBUG): # the stdlib level predicate log.debug(expensive())These are stdlib semantics; EdgeCommons only configures the root logger, level, format, and sinks.
Rust has no logger object — components use the tracing macros, and EdgeCommons installs the
subscriber that decides what is emitted:
tracing::trace!(detail = ?value, "trace line");tracing::debug!(payload_bytes = payload.len(), "received payload");tracing::info!(publish_interval = interval, "configuration changed");tracing::warn!(error = %err, "retrying after error");tracing::error!("query failed");Structured key/value fields are preserved verbatim in the JSON sink. Level “predicates” are handled
by tracing itself (the subscriber and EnvFilter); there is no EdgeCommons-provided isEnabled
helper.
Logger is an exported class. Construction is normally via getLogger(name) / the logger
singleton rather than new:
export class Logger { readonly loggerName: string; constructor(name?: string); // defaults to "edgecommons"
debug(message: string, error?: unknown): void; info(message: string, error?: unknown): void; warn(message: string, error?: unknown): void; error(message: string, error?: unknown): void;
refresh(): void; // recompute level/format/sink from config setLevel(level: Level): void; // Level enum is internal — see note setFormat(format: string | undefined): void; // ts_format template; undefined → default setFileWriter(writer: RotatingFileWriter | undefined): void;}There are only four log methods (debug/info/warn/error) — no trace/fatal, and no
level-predicate methods. Each method takes an optional second error?: unknown. In text mode
warn/error write to stderr and the rest to stdout; in JSON mode all levels go to stdout.
The logging configuration type
Section titled “The logging configuration type”These types model the logging config section and drive the configurator. They are framework-facing —
you rarely construct them yourself — but they expose the resolved values via getters.
com.mbreissi.edgecommons.config.LoggingConfiguration parses the logging JSON object and exposes
typed getters:
LoggingConfiguration(JsonObject json);
org.apache.logging.log4j.Level getLevel(); // default INFOString getFormat(); // java_format or DEFAULT_FORMATboolean isFormatExplicitlySet(); // true if java_format was providedboolean isFileLoggingEnabled();String getLogFilePath(); // == fileLogging.filePathString getMaxFileSize(); // default "10MB"int getBackupCount(); // default 5java.util.Map<String, Level> getLoggerLevels(); // from logging.loggersboolean isGlobalControlEnabled(); // default falseLogPublishConfiguration getPublishConfig(); // logging.publishDefaults: level INFO, format
%d{yyyy-MM-dd HH:mm:ss} [%-5p] %-25.25c{1}(%4L): %m%n, maxFileSize "10MB", backupCount 5.
Obtain the live instance via ConfigManager.getLoggingConfig().
edgecommons.config.enhanced_logging_config.EnhancedLoggingConfiguration parses the section, applies
it, and exposes getters:
EnhancedLoggingConfiguration( logging_config=None, # the logging dict platform_default_format=None, # e.g. "json" on KUBERNETES correlation=None, # thing/pod/namespace/node fields)
cfg.configure_logging(config_manager=None) # clears root handlers, installs sinkscfg.get_level(); cfg.get_format(); cfg.is_json_sink()cfg.is_file_logging_enabled(); cfg.get_log_file_path()cfg.get_logger_levels(); cfg.is_global_control_enabled()cfg.get_publish_config(); cfg.to_dict()Defaults: level logging.INFO, format %(asctime)s [%(levelname)s] %(name)s: %(message)s. The
companion JsonLogFormatter(logging.Formatter) renders the JSON sink (UTC timestamps via
time.gmtime). Obtain the live instance via ConfigManager.get_logging_config().
Rust has no separate configuration class — logging::init/logging::reconfigure read the parsed
logging section off config.parsed.logging:
// fields read from config.parsed.logginglevel: String, // default "INFO"rust_format: Option<String>, // token template or "json"loggers: HashMap<String, String>, // target -> level (EnvFilter directives)file_logging.enabled: bool,file_logging.file_path: String,file_logging.max_file_size(): String, // default "10MB"file_logging.backup_count(): u32, // default 5publish: LoggingPublishConfig, // logging.publishThere are no getters to call; the values are consumed directly by the configurator.
TypeScript has no exported configuration class — initLogging/reconfigureLogging read the parsed
logging section off config.parsed.logging:
// fields read from config.parsed.logginglevel: string; // default "INFO"tsFormat?: string; // token template or "json"loggers: Record<string, string>; // logger name -> levelfileLogging.enabled: boolean;fileLogging.filePath: string;fileLogging.maxFileSize(): string; // default "10MB"fileLogging.backupCount(): number; // default 5publish: LoggingPublishConfig;The platform default (e.g. json on Kubernetes) is supplied through LoggingOptions:
interface LoggingOptions { formatDefault?: string; // platform-profile default format env?: Env; // environment lookup for correlation fields}Configurator and reconfiguration entry points
Section titled “Configurator and reconfiguration entry points”The framework calls these at startup and on hot reload. They are documented here for completeness; component authors do not normally invoke them.
// on ConfigManager — the live runtime configuratorpublic void reconfigureLogging(); // rebuilds the Log4j2 config from LoggingConfigurationpublic LoggingConfiguration getLoggingConfig();reconfigureLogging() runs at init and again on hot reload via the registered
LoggingConfigChangeListener. It always installs a SYSTEM_OUT console appender, optionally a
size-rotated RollingFile appender, sets the root level, and adds a per-logger LoggerConfig
(with additivity=false) for each logging.loggers entry. When globalControl=true it delegates to
GlobalLoggingManager, which rebuilds the entire Log4j2 configuration.
EnhancedLoggingConfiguration(...).configure_logging(config_manager=None)configure_logging() clears the root handlers, sets the root level, installs a StreamHandler (JSON
or text formatter), and — only when fileLogging.enabled and not under the JSON sink — a
logging.handlers.RotatingFileHandler(maxBytes, backupCount). The ConfigManager rebuilds and calls
this on hot reload. Note: Python parses globalControl (is_global_control_enabled()) but
configure_logging() never acts on it.
use edgecommons::{config::model::Config, logging};
logging::init(&config, profile_format_default); // install the global subscriber once (idempotent)logging::reconfigure(&config); // swap the level EnvFilter liveinit takes the parsed Config and an Option<&str> platform default (pass Some("json") for the
Kubernetes profile). It installs the global tracing-subscriber exactly once. reconfigure swaps
only the level filter live — the format and file layers are fixed at init, so a rust_format or
fileLogging change requires a restart. LoggingReconfigurer implements ConfigurationChangeListener
to call reconfigure on reload.
import { initLogging, reconfigureLogging, LoggingReconfigurer } from "@edgecommons/edgecommons";
initLogging(config, options?); // (config: Config, options?: LoggingOptions)reconfigureLogging(config); // re-run the full configuratorinitLogging configures all Logger instances from config.parsed.logging (level, per-logger
levels, format/JSON mode, and the rotating file writer). reconfigureLogging re-runs the full
configurator on hot reload and can swap the file writer live. LoggingReconfigurer implements
ConfigurationChangeListener and forwards reload events to reconfigureLogging.
Structured log bus publisher
Section titled “Structured log bus publisher”The log UNS class is library-owned and reserved. When logging.publish.enabled is true,
EdgeCommons publishes structured records to:
ecv1/{device}/{component}/log/{level}Each message uses envelope header { name: "log", version: "1.0" } and body schema
edgecommons.log.v1. The core body fields are timestamp, level, logger, message, and
sequence; optional fields include thread, fields, error, truncated, and dropped.
Application code still cannot publish raw messages to .../log/... through the public messaging API;
the log publisher uses the internal reserved publish seam.
LogService logs = gg.getLogs();logs.publish(LogRecord.builder() .withLevel("INFO") .withLogger("app") .withMessage("started") .addField("instance", "main") .build());logs.flush(Duration.ofSeconds(2));LogStats stats = logs.stats();from edgecommons.logs import LogRecord
gg.logs().publish(LogRecord( level="INFO", logger="app", message="started", fields={"instance": "main"},))gg.logs().flush(timeout=2.0)stats = gg.logs().stats()use edgecommons::logs::{LogLevel, LogRecord};
gg.logs().try_publish( LogRecord::builder(LogLevel::Info, "app", "started") .field("instance", serde_json::json!("main")) .build(),)?;let stats = gg.logs().stats();await gg.logs().publish({ level: "INFO", logger: "app", message: "started", fields: { instance: "main" },});await gg.logs().flush();const stats = gg.logs().stats();Automatic capture is controlled by logging.publish.captureNative and
logging.publish.captureConsole. Native capture observes the logging framework that the SDK wires:
Log4j2/JUL/SLF4J in Java, stdlib logging in Python, tracing in Rust, and EdgeCommons Logger in
TypeScript. Console/stdout capture is runtime-dependent; the config field is shared, but not every SDK
can intercept arbitrary third-party output.
Format tokens
Section titled “Format tokens”Each SDK reads only its own format key, written in its own syntax; a token from one language
is meaningless to the others. Setting the value to the literal json (case-insensitive) selects the
stdout-JSON sink instead of a text layout.
| SDK | Config key | Syntax | Default (when unset) |
|---|---|---|---|
| Java | java_format |
Log4j2 PatternLayout |
%d{yyyy-MM-dd HH:mm:ss} [%-5p] %-25.25c{1}(%4L): %m%n |
| Python | python_format |
logging.Formatter string |
%(asctime)s [%(levelname)s] %(name)s: %(message)s |
| Rust | rust_format |
token template | none — the tracing-subscriber fmt layer (the {timestamp} {level} {component}/{componentName} {target} {message} tokens apply only when rust_format is set explicitly) |
| TypeScript | ts_format |
token template | {timestamp} [{level}] {message} |
Token-template placeholders differ between Rust and TypeScript: Rust supports {timestamp},
{level}, {target}, {message} (it uses {target}), while TypeScript supports {timestamp},
{level}, {logger}, {message} (it uses {logger}). The effective format is resolved with the
precedence: explicit *_format token, then the platform-profile default (json on Kubernetes), then
the SDK’s built-in text default.
Example: get a logger and log
Section titled “Example: get a logger and log”import com.mbreissi.edgecommons.logging.Logger;import com.mbreissi.edgecommons.logging.LoggerFactory;
Logger log = LoggerFactory.getLogger(MyApp.class); // or getLogger("my.logger")
log.info("publish interval set to {}ms", interval); // SLF4J-style {} argslog.warn("retrying after error", throwable); // (String, Throwable) overloadif (log.isDebugEnabled()) { log.debug("payload bytes: {}", payload.length);}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))use tracing::{debug, info, warn};
info!(publish_interval = interval, "configuration changed");warn!(error = %err, "retrying after error");debug!(payload_bytes = payload.len(), "received payload");import { logger, getLogger } from "@edgecommons/edgecommons";
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?) signature