| name | boxlang-core-dev-logging |
| description | Use this skill when working with BoxLang logging: obtaining loggers via LoggingService, using BoxLangLogger (trace/debug/info/warn/error), pre-configured common loggers, creating named loggers, parameterized messages, accessing loggers from context and interceptors, and logging configuration in boxlang.json. |
BoxLang Logging
Overview
BoxLang uses a centralized logging system built on Logback (SLF4J implementation).
All logging MUST go through LoggingService — never use System.out.println() or
create SLF4J Logger instances directly.
Key classes:
ortus.boxlang.runtime.logging.LoggingService — singleton service managing all loggers
ortus.boxlang.runtime.logging.BoxLangLogger — wrapper around SLF4J LocationAwareLogger
Getting a Logger
Pre-Configured Common Loggers
import ortus.boxlang.runtime.logging.BoxLangLogger;
import ortus.boxlang.runtime.services.LoggingService;
LoggingService loggingService = BoxRuntime.getInstance().getLoggingService();
BoxLangLogger logger = loggingService.RUNTIME_LOGGER;
BoxLangLogger logger = loggingService.ASYNC_LOGGER;
BoxLangLogger logger = loggingService.CACHE_LOGGER;
BoxLangLogger logger = loggingService.EXCEPTION_LOGGER;
BoxLangLogger logger = loggingService.DATASOURCE_LOGGER;
BoxLangLogger logger = loggingService.MODULES_LOGGER;
BoxLangLogger logger = loggingService.SCHEDULER_LOGGER;
BoxLangLogger logger = loggingService.APPLICATION_LOGGER;
Creating Named Loggers
LoggingService loggingService = BoxRuntime.getInstance().getLoggingService();
BoxLangLogger logger = loggingService.getLogger( "myfeature" );
BoxLangLogger logger = loggingService.getLogger( "subsystem/component" );
BoxLangLogger logger = loggingService.getLogger( "/var/log/boxlang/custom.log" );
getLogger() automatically appends .log if no extension is present, creates the
logger lazily, and caches it for reuse (case-insensitive).
From Context
BoxLangLogger logger = context.getLogger();
BoxLangLogger logger = this.getLogger();
BoxLangLogger logger = this.getLogger();
Logging Methods
logger.trace( "Entering method with params: {}", params );
logger.debug( "Processing {} items in batch", items.size() );
logger.info( "Server started on port {}", port );
logger.warn( "Cache threshold exceeded: {} of {}", size, max );
logger.error( "Connection failed: {}", e.getMessage() );
logger.error( "Unexpected exception", e );
Key Practices
Always Use Parameterized Messages
logger.debug( "User {} logged in from {}", username, ipAddress );
logger.debug( "User " + username + " logged in from " + ipAddress );
Include Exceptions When Logging Errors
try {
processRequest( context );
} catch ( Exception e ) {
logger.error( "Failed to process request for {}", requestPath, e );
}
Choose the Right Logger
| Logger | Use for |
|---|
RUNTIME_LOGGER | Core runtime events, service start/stop |
MODULES_LOGGER | Module loading, registration, unloading |
ASYNC_LOGGER | Thread pools, futures, async work |
SCHEDULER_LOGGER | Scheduled tasks, cron jobs |
CACHE_LOGGER | Cache hits, misses, eviction |
DATASOURCE_LOGGER | Query execution, connection management |
EXCEPTION_LOGGER | Caught exceptions for audit/analysis |
APPLICATION_LOGGER | Application lifecycle events |
| Named logger | New features or subsystems needing isolation |
Logging in Common Patterns
Service Initialization
public class MyService extends BaseService {
private BoxLangLogger logger;
@Override
public void onStartup() {
this.logger = BoxRuntime.getInstance().getLoggingService().getLogger( "myservice" );
logger.info( "MyService starting up..." );
}
@Override
public void onShutdown( Boolean force ) {
logger.info( "MyService shutting down (force={})", force );
}
}
BIF with Logging
@BoxBIF
public class MyBIF extends BIF {
private static final BoxLangLogger logger =
BoxRuntime.getInstance().getLoggingService().getLogger( "bifs.mybif" );
@Override
public Object invoke( IBoxContext context, ArgumentsScope arguments ) {
String input = arguments.getAsString( Key.of( "input" ) );
logger.debug( "Processing input of length {}", input.length() );
try {
Object result = doWork( input );
logger.trace( "Result computed successfully" );
return result;
} catch ( Exception e ) {
logger.error( "BIF processing failed for input: {}", input, e );
throw new BoxRuntimeException( "Processing error: " + e.getMessage(), e );
}
}
}
Interceptor with Logging
@Interceptor
public class AuditInterceptor extends BaseInterceptor {
@Override
public void configure() {
this.log.info( "AuditInterceptor configured" );
}
@InterceptionPoint
public void onRequestStart( IStruct event ) {
this.log.info( "Request started: {}", event.getAsString( Key.of("path") ) );
}
}
Scheduler with Logging
public class MyScheduler extends BaseScheduler {
public MyScheduler() {
super( "my-scheduler" );
}
@Override
public void configure() {
task( "cleanupTask" )
.call( () -> {
this.logger.info( "Running cleanup task" );
performCleanup();
})
.everyHour();
}
}
ModuleConfig.bx (BoxLang)
// ModuleConfig.bx — log is auto-injected
class {
function onLoad() {
// log is already available — no setup needed
log.info( "Module loaded. API URL: #settings.baseUrl#" )
}
function configure() {
log.debug( "Configuring module with apiKey length: #len(settings.apiKey)#" )
}
function onUnload() {
log.info( "Module unloaded." )
}
}
Logging Configuration (boxlang.json)
{
"logging": {
"logsDirectory": "./logs",
"level": "INFO",
"loggers": {
"scheduler": { "level": "DEBUG", "async": true },
"cache": { "level": "WARN" }
}
}
}
Supported levels (from most to least verbose): TRACE, DEBUG, INFO, WARN, ERROR
What NOT to Do
System.out.println( "My debug message" );
System.err.println( "My error" );
private static final Logger log = LoggerFactory.getLogger( MyClass.class );
logger.info( "Processing " + count + " items" );
logger.debug( "Password: {}", user.getPassword() );
logger.trace( "Token: {}", authToken );
References