boxlang-core-dev-logging

Installation
SKILL.md

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();

// 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 );

References

Related skills

More from ortus-boxlang/skills

Installs
4
First Seen
Apr 21, 2026