Logging
BoxLang AI skills repository and Claude Plugin
npx -y skills add ortus-boxlang/skills --skill loggingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
7.9 KB, as published. Nobody here has run it
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 loggersortus.boxlang.runtime.logging.BoxLangLogger— wrapper around SLF4JLocationAwareLogger
Getting a Logger
Pre-Configured Common Loggers
import ortus.boxlang.runtime.logging.BoxLangLogger;
import ortus.boxlang.runtime.services.LoggingService;
LoggingService loggingService = BoxRuntime.getInstance().getLoggingService();
// Common loggers — already initialized, ready to use
BoxLangLogger logger = loggingService.RUNTIME_LOGGER; // Core runtime events
BoxLangLogger logger = loggingService.ASYNC_LOGGER; // Async operations
BoxLangLogger logger = loggingService.CACHE_LOGGER; // Cache operations
BoxLangLogger logger = loggingService.EXCEPTION_LOGGER; // Exception tracking
BoxLangLogger logger = loggingService.DATASOURCE_LOGGER; // Database operations
BoxLangLogger logger = loggingService.MODULES_LOGGER; // Module loading/lifecycle
BoxLangLogger logger = loggingService.SCHEDULER_LOGGER; // Scheduled tasks
BoxLangLogger logger = loggingService.APPLICATION_LOGGER;// Application-level events
Creating Named Loggers
LoggingService loggingService = BoxRuntime.getInstance().getLoggingService();
// Named logger (logs to logs/myfeature.log)
BoxLangLogger logger = loggingService.getLogger( "myfeature" );
// Relative path (relative to configured logs directory)
BoxLangLogger logger = loggingService.getLogger( "subsystem/component" );
// Absolute path
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
// Any IBoxContext provides a logger
BoxLangLogger logger = context.getLogger();
// From BaseInterceptor subclass
BoxLangLogger logger = this.getLogger();
// From BaseScheduler subclass
BoxLangLogger logger = this.getLogger();
Logging Methods
logger.trace( "Entering method with params: {}", params ); // Very detailed
logger.debug( "Processing {} items in batch", items.size() ); // Debug info
logger.info( "Server started on port {}", port ); // Key events
logger.warn( "Cache threshold exceeded: {} of {}", size, max ); // Warnings
logger.error( "Connection failed: {}", e.getMessage() ); // Errors
logger.error( "Unexpected exception", e ); // With stack trace
Key Practices
Always Use Parameterized Messages
// CORRECT — message built only when level is enabled
logger.debug( "User {} logged in from {}", username, ipAddress );
// WRONG — concatenation happens regardless of log level
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 is automatically injected by BaseInterceptor
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" );
// this.logger is available in BaseScheduler
}
@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
// ❌ Never use System.out / System.err
System.out.println( "My debug message" );
System.err.println( "My error" );
// ❌ Never create SLF4J loggers directly
private static final Logger log = LoggerFactory.getLogger( MyClass.class );
// ❌ Never concatenate in log calls
logger.info( "Processing " + count + " items" ); // Use {} placeholders instead
// ❌ Never log sensitive data
logger.debug( "Password: {}", user.getPassword() );
logger.trace( "Token: {}", authToken );