The centralized, context-aware logging service for the GraphDB Workbench microfrontend architecture.
This guide explains the centralized logging service implementation in the GraphDB Workbench microfrontend architecture. The logging service provides context-aware logging with configurable output destinations and supports multiple logger implementations.
The logging service is designed to provide consistent logging across all microfrontend modules while allowing flexibility in how and where logs are output. Each module can have its own logger instance with automatic context identification, making debugging and monitoring 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:
export enum LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3
}
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)],
]);
The logger.config.json file controls which loggers are active:
{
"minLogLevel": 0,
"loggers": ["console"]
}
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);
}
// ...other logging methods
}
The Loggers class exposes factory method to get module-specific logger instances:
export class Loggers {
private static loggerInstances = new Map<Module, 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 database logger or file 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);
console.log(`[DB-FALLBACK] ${message}`);
});
}
}
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
]);
Update the logger.config.json to include your new logger:
{
"minLogLevel": 0,
"loggers": ["console", "database"]
}
The loggers array determines which logger implementations will be active. You can include multiple loggers to send the same log message to different destinations simultaneously.
// In any microfrontend module define a module specific wrapper for the logger
import { Loggers } from '@ontotext/workbench-api';
const MODULE_NAME = 'Workbench';
/**
* Logger for the Workbench module.
*/
export class WorkbenchLoggerService {
/**
* Gets the logger instance for the Workbench module.
*
* @returns LoggerService instance for the Workbench module
*/
static get logger() {
return Loggers.getLoggerInstance(MODULE_NAME);
}
}
Then use it in a file:
const logger = WorkbenchLoggerService.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:
{
"minLogLevel": 1,
"loggers": ["console", "database"]
}
With this configuration, calling logger.info('User logged in') will:
The system respects log level filtering. Messages below the configured level are ignored:
DEBUG = 0: Shows all messagesINFO = 1: Shows info, warning, and error messagesWARN = 2: Shows only warning and error messagesERROR = 3: Shows only error messagesThe logging service automatically adjusts behavior based on the environment:
logger.config.jsonINFO regardless of configurationSee also: Developers Guide