The centralized, context-aware logging service for the GraphDB Workbench microfrontend architecture.
LoggerService and the Loggers factory.The logging service provides consistent logging across all microfrontend modules while allowing flexibility in how and where logs are output. Each module has its own logger instance that tags every message with the module name, making debugging easier across the distributed application.
The logging system consists of several key components:
The Logger interface defines the contract that all logger implementations must follow:
export interface Logger {
/**
* Logs a message based on the specified log level.
* @param level - The log level determining output behavior
* @param message - The message to log
* @param args - Additional arguments for message formatting
*/
log(level: LogLevel, message: string, args: unknown[]): void;
}
Log levels determine the importance and filtering of messages (log-level.ts).
The values are strings; toNumericLogLevel() maps them to 0–3 for comparison:
export enum LogLevel {
DEBUG = 'DEBUG',
INFO = 'INFO',
WARN = 'WARN',
ERROR = 'ERROR'
}
Defines the available logger implementations:
export enum LoggerType {
CONSOLE = 'console',
}
The logger-definitions.ts file contains a map of available logger implementations:
export const LOGGER_DEFINITIONS = new Map<LoggerType, Logger>([
[LoggerType.CONSOLE, service(ConsoleLoggerService)],
]);
Which loggers are active and the minimum level come from the loggerConfig field
(LoggerConfig) of the application configuration. The
default configuration is assets/configuration.default.json; an env.json
can point configUrl at another file (see packages/api/src/services/configuration/env.default.json).
{
"loggerConfig": {
"loggers": ["console"],
"minLogLevel": "DEBUG"
}
}
LoggerService reads it from ConfigurationContextService on every log call.
The main service that coordinates logging across different implementations (logger-service.ts):
export class LoggerService {
constructor(module: string) {
this.module = module;
}
debug(message: string, ...args: unknown[]): void {
this.log(LogLevel.DEBUG, message, args);
}
// ...info, warn, error
}
A logger type listed in loggerConfig.loggers but missing from LOGGER_DEFINITIONS throws an Error.
The Loggers class exposes a factory method that returns one logger instance per module:
export class Loggers {
private static readonly loggerInstances = new Map<string, LoggerService>();
static getLoggerInstance(module: string): LoggerService {
if (!this.loggerInstances.has(module)) {
this.loggerInstances.set(module, new LoggerService(module));
}
return this.loggerInstances.get(module)!;
}
}
To add a new logger (e.g., a remote logger), follow these steps:
Create a new logger class that implements the Logger interface:
// packages/api/src/services/logging/database/database-logger.service.ts
export class DatabaseLoggerService implements Logger {
log(level: LogLevel, message: string): void {
const logEntry = { level, message, timestamp: new Date().toISOString() };
fetch('/api/logs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(logEntry)
}).catch(error => {
console.error('Database logging failed:', error);
});
}
}
Add your new logger type to the LoggerType enumeration:
export enum LoggerType {
CONSOLE = 'console',
DATABASE = 'database' // Add your new logger type
}
Add your logger to the LOGGER_DEFINITIONS map, which will create its instance:
export const LOGGER_DEFINITIONS = new Map<LoggerType, Logger>([
[LoggerType.CONSOLE, service(ConsoleLoggerService)],
[LoggerType.DATABASE, service(DatabaseLoggerService)], // define the new logger here
]);
Add the new type to loggerConfig.loggers in the application configuration
(assets/configuration.default.json):
{
"loggerConfig": {
"loggers": ["console", "database"],
"minLogLevel": "DEBUG"
}
}
The loggers array determines which logger implementations are active. Every message goes to all of them.
Each package defines a local LoggerProvider that wraps Loggers.getLoggerInstance(MODULE_NAME) — see the
logging conventions. Then use it in a file:
const logger = LoggerProvider.logger;
logger.info('SPARQL query executed successfully', { duration: 234 });
logger.error('Database connection failed', { error: 'timeout' });
Output example:
[INFO] [Workbench] [9/11/2025, 2:30:45 PM] SPARQL query executed successfully {duration: 234}
[ERROR] [Workbench] [9/11/2025, 2:30:46 PM] Database connection failed {error: 'timeout'}
When multiple loggers are configured, the same message is sent to all active destinations:
{
"loggerConfig": {
"loggers": ["console", "database"],
"minLogLevel": "INFO"
}
}
With this configuration, calling logger.info('User logged in') will:
Messages below minLogLevel are ignored:
DEBUG: Shows all messagesINFO: Shows info, warning, and error messagesWARN: Shows only warning and error messagesERROR: Shows only error messagesThere is no environment-specific override: the level comes only from the configuration file.
See also: Developers Guide