Hooks and Monitors
Hooks and Monitors
Lucee provides two powerful extension mechanisms that allow you to inject custom functionality at different points in the application lifecycle: Hooks and Monitors. These systems enable you to extend Lucee's capabilities, implement custom initialization logic, collect runtime metrics, and integrate with external systems seamlessly.
AI-Optimized Technical Reference
For developers using AI assistance or requiring structured technical data, see the companion specification: Hooks Monitors
Hooks
Hooks allow you to inject custom classes that execute at specific points during Lucee's lifecycle. The primary hook type is the startup hook, which runs during Lucee initialization.
Startup Hooks
Startup hooks are executed when Lucee starts up, allowing you to perform initialization tasks, configure services, or set up integrations before your applications begin processing requests. They are ideal for:
- Database connection pool initialization
- External service integration setup
- Custom service registration
- WebSocket endpoint registration
- Configuration validation and setup
- Resource pre-loading
Configuration
Startup hooks can be configured in multiple ways depending on your use case and deployment strategy.
Configuration via .CFConfig.json:
You can define startup hooks directly in your .CFConfig.json
configuration file. This method is ideal for application-specific hooks.
{
"startupHooks": [
{
"class": "com.mycompany.hooks.DatabaseInitializer"
}
]
}
OSGi-based Hook Example:
{
"startupHooks": [
{
"class": "com.mycompany.hooks.DatabaseInitializer",
"bundleName": "my-database-initializer",
"bundleVersion": "1.0.0"
}
]
}
Maven-based Hook Example:
{
"startupHooks": [
{
"class": "com.example.monitoring.PerformanceHook",
"maven": "com.example:performance-monitor:2.1.0"
}
]
}
Component-based Hook Example (Lucee 7 only):
{
"startupHooks": [
{
"component": "hooks.ApplicationInitializer"
}
]
}
Implementation
Java Class Implementation:
When implementing a startup hook as a Java class, you have two constructor options:
package com.mycompany.hooks;
import lucee.runtime.config.Config;
import lucee.runtime.config.ConfigServer;
import lucee.runtime.config.ConfigWeb;
import lucee.loader.engine.CFMLEngine;
import lucee.loader.engine.CFMLEngineFactory;
public class MyStartupHook {
private final ConfigServer configServer;
private final CFMLEngine engine;
private static MyStartupHook instance = null;
private boolean alive = true;
public MyStartupHook(Config config) {
try {
this.configServer = (ConfigServer) config;
this.engine = CFMLEngineFactory.getInstance();
// Initialize services
initializeServices();
// Set singleton instance for access from other parts of the application
instance = this;
} catch (RuntimeException re) {
System.err.println("Failed to initialize MyStartupHook: " + re.getMessage());
throw re;
}
}
private void initializeServices() {
// Initialize for all web contexts
for (ConfigWeb cw : configServer.getConfigWebs()) {
try {
if (cw != null && cw.getServletContext() != null) {
initializeWebContext(cw);
}
} catch (Exception e) {
System.err.println("Error initializing web context " +
cw.getIdentification().getId() + ": " + e.getMessage());
}
}
}
private void initializeWebContext(ConfigWeb config) {
System.out.println("Initializing services for web context: " +
config.getIdentification().getId());
// Your web-context specific initialization
}
public static MyStartupHook getInstance() throws Exception {
if (instance == null) {
throw new Exception("MyStartupHook failed to initialize within the Lucee engine");
}
return instance;
}
public boolean isAlive() {
return alive;
}
public void finalize() {
alive = false;
System.out.println("MyStartupHook finalizing...");
}
}
CFML Component Implementation:
You can also implement hooks as CFML components:
// hooks/ApplicationInitializer.cfc
component {
public function init() {
// Initialization logic
writeOutput("Application hook initialized at " & now());
// Example: Set up application-wide variables
application.startupTime = now();
application.hookVersion = "1.0.0";
return this;
}
public boolean function isAlive() {
return true;
}
public void function finalize() {
}
}
Use Cases
WebSocket Endpoint Registration:
public class WebSocketHook {
private boolean isEndpointRegistered = false;
private final Object registrationLock = new Object();
public WebSocketHook(Config config) {
this.configServer = (ConfigServer) config;
registerWebSocketEndpoints();
}
private void registerWebSocketEndpoints() {
for (ConfigWeb cw : configServer.getConfigWebs()) {
try {
if (cw != null && cw.getServletContext() != null) {
registerEndpointForContext(cw);
}
} catch (Exception e) {
System.err.println("Failed to register WebSocket endpoint: " + e.getMessage());
}
}
}
private void registerEndpointForContext(ConfigWeb cw) throws Exception {
if (!isEndpointRegistered) {
synchronized (registrationLock) {
if (!isEndpointRegistered) {
Object serverContainer = cw.getServletContext()
.getAttribute("jakarta.websocket.server.ServerContainer");
if (serverContainer != null) {
((jakarta.websocket.server.ServerContainer) serverContainer)
.addEndpoint(MyWebSocketEndpoint.class);
isEndpointRegistered = true;
System.out.println("WebSocket endpoint registered successfully");
}
}
}
}
}
}
Deployment via Extension
Extensions can declare startup hooks in their META-INF/MANIFEST.MF
file:
Manifest-Version: 1.0
Built-Date: 2025-08-18 22:53:56
version: "3.0.0.17"
id: "3F9DFF32-B555-449D-B0EB5DB723044045"
name: WebSockets Extension
description: Websocket integration into Lucee.
start-bundles: true
release-type: server
startup-hook: "{'class':'org.lucee.extension.websocket.WebSocketEndpointFactory','name':'org.lucee.websocket.extension','version':'3.0.0.17'}"
lucee-core-version: "5.3.0.20"
Monitors
Monitors provide continuous observation and data collection about Lucee's runtime behavior. Unlike startup hooks that run once during initialization, monitors are invoked throughout the application lifecycle to track performance metrics, system health, and operational data.
Monitoring is only active when enabled in configuration:
{
"monitoring": {
"enabled": true
}
}
Monitors can be configured in two ways:
- Via Extension Manifest - Packaged with extensions for reusable monitoring functionality
- Via .CFConfig.json - Direct configuration for application-specific monitoring
Configuration via .CFConfig.json
You can define monitors directly in your .CFConfig.json
configuration file under the monitoring
section:
{
"monitoring": {
"enabled": true,
"monitor": [
{
"name": "MyActionMonitor",
"type": "action",
"class": "com.mycompany.monitors.ActionMonitorImpl",
"bundleName": "my.monitor.bundle",
"bundleVersion": "1.0.0",
"log": true,
"async": false
},
{
"name": "MyRequestMonitor",
"type": "request",
"class": "com.mycompany.monitors.RequestMonitorImpl",
"maven": "com.mycompany:monitor-lib:2.1.0",
"log": true,
"async": true
},
{
"name": "MyIntervalMonitor",
"type": "interval",
"component": "monitors.SystemMonitor",
"log": true
}
]
}
}
Configuration Properties:
name
: Unique identifier for the monitor (required)type
: Monitor type -action
,request
, orinterval
(required)class
: Full Java class name (required unless using component)component
: CFML component path (alternative to class)bundleName
: OSGi bundle name (optional, for OSGi resolution)bundleVersion
: OSGi bundle version (optional, for OSGi resolution)maven
: Maven coordinates in formatgroupId:artifactId:version
(alternative to OSGi)log
: Enable/disable logging for this monitor (optional, default: true)async
: Run request monitors asynchronously (optional, request monitors only, default: false)
Class Definition Resolution Order:
- OSGi Bundle - If
bundleName
is provided - Maven Dependency - If
maven
is provided - CFML Component - If
component
is provided (andclass
is null) - Standard Classpath - Fallback for simple class loading
There are three types of monitors, each serving different monitoring needs:
Action Monitors
Action monitors track specific operations within Lucee as they occur, such as database queries, lock operations, mail sending, and cache operations. They are called synchronously during the execution of these operations.
Interface Implementation
package com.mycompany.monitors;
import java.io.IOException;
import java.util.Map;
import lucee.runtime.PageContext;
import lucee.runtime.config.ConfigWeb;
import lucee.runtime.exp.PageException;
import lucee.runtime.monitor.ActionMonitor;
import lucee.runtime.type.Query;
public class MyActionMonitor implements ActionMonitor {
@Override
public void log(PageContext pc, String type, String label, long executionTime, Object data) throws IOException {
// Log action within a request context
System.out.println("Action: " + type + " | Label: " + label +
" | Time: " + executionTime + "ms | Data: " + data);
// Examples of what Lucee logs:
// type="query", label="Query", data=queryResult
// type="lock", label="Lock", data="lockName:timeoutInMillis"
// type="cache", label="Cache", data=cacheOperation
// type="mail", label="Mail", data=mailProperties
}
@Override
public void log(ConfigWeb config, String type, String label, long executionTime, Object data) throws IOException {
// Log action outside of request context (e.g., scheduled tasks, mail sending)
System.out.println("Background Action: " + type + " | Label: " + label +
" | Time: " + executionTime + "ms");
}
@Override
public Query getData(Map<String, Object> arguments) throws PageException {
// Return collected action data as a query for reporting
// Filter by arguments like date range, type, etc.
return createActionDataQuery(arguments);
}
private Query createActionDataQuery(Map<String, Object> arguments) throws PageException {
// Implementation to return your collected data
// Columns typically include: timestamp, type, label, executionTime, details
return null; // Implement based on your storage mechanism
}
// Base Monitor interface methods
@Override
public void init(ConfigWeb config, String name, Map<String, String> arguments) throws IOException {
System.out.println("Initializing ActionMonitor: " + name);
}
@Override
public String getName() {
return "MyActionMonitor";
}
@Override
public boolean isEnabled() {
return true;
}
}
Use Cases
Action monitors are ideal for:
- Database query performance tracking
- Lock contention analysis
- Mail delivery monitoring
- Cache hit/miss ratio tracking
- Custom operation timing
Request Monitors
Request monitors collect data about each HTTP request processed by Lucee. They are called at the end of each request and can track both successful and error requests.
Interface Implementation
package com.mycompany.monitors;
import java.io.IOException;
import java.util.Map;
import lucee.runtime.PageContext;
import lucee.runtime.config.ConfigWeb;
import lucee.runtime.exp.PageException;
import lucee.runtime.monitor.RequestMonitor;
import lucee.runtime.type.Query;
public class MyRequestMonitor implements RequestMonitor {
@Override
public void log(PageContext pc, boolean error) throws IOException {
// Called at the end of each request
long requestTime = System.currentTimeMillis() - pc.getStartTime();
String requestURI = pc.getHttpServletRequest().getRequestURI();
System.out.println("Request: " + requestURI +
" | Time: " + requestTime + "ms" +
" | Error: " + error);
// Collect additional request metrics:
// - Memory usage during request
// - User agent, IP address
// - Response size
// - Custom application metrics
}
@Override
public Query getData(ConfigWeb config, Map<String, Object> arguments) throws PageException {
// Return request data for the specific web context
// Filter by arguments like date range, error status, etc.
return createRequestDataQuery(config, arguments);
}
private Query createRequestDataQuery(ConfigWeb config, Map<String, Object> arguments) throws PageException {
// Implementation to return collected request data
// Columns typically include: timestamp, uri, executionTime, error, userAgent, etc.
return null; // Implement based on your storage mechanism
}
@Override
public void init(ConfigWeb config, String name, Map<String, String> arguments) throws IOException {
System.out.println("Initializing RequestMonitor: " + name + " for context: " +
config.getIdentification().getId());
}
@Override
public String getName() {
return "MyRequestMonitor";
}
@Override
public boolean isEnabled() {
return true;
}
}
Use Cases
Request monitors are ideal for:
- Application performance monitoring (APM)
- Error rate tracking
- User behavior analysis
- Load pattern identification
- Security monitoring
Interval Monitors
Interval monitors collect system and application metrics at regular intervals (every 5000ms by default). They run independently of user requests and are perfect for system health monitoring.
Interface Implementation
package com.mycompany.monitors;
import java.io.IOException;
import java.util.Map;
import lucee.runtime.config.ConfigWeb;
import lucee.runtime.exp.PageException;
import lucee.runtime.monitor.IntervallMonitor;
import lucee.runtime.type.Query;
public class MyIntervalMonitor implements IntervallMonitor {
@Override
public void log() throws IOException {
// Called every 5000ms (configurable)
Runtime runtime = Runtime.getRuntime();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
System.out.println("System Stats - Used Memory: " + (usedMemory / 1024 / 1024) + "MB" +
" | Free Memory: " + (freeMemory / 1024 / 1024) + "MB" +
" | Total Memory: " + (totalMemory / 1024 / 1024) + "MB");
// Collect additional metrics:
// - CPU usage
// - Thread counts
// - Database connection pool status
// - Cache statistics
// - Custom application metrics
}
@Override
public Query getData(Map<String, Object> arguments) throws PageException {
// Return interval data collected over time
// Filter by arguments like date range, metric type, etc.
return createIntervalDataQuery(arguments);
}
private Query createIntervalDataQuery(Map<String, Object> arguments) throws PageException {
// Implementation to return collected interval data
// Columns typically include: timestamp, memoryUsed, memoryFree, cpuUsage, etc.
return null; // Implement based on your storage mechanism
}
@Override
public void init(ConfigWeb config, String name, Map<String, String> arguments) throws IOException {
System.out.println("Initializing IntervalMonitor: " + name);
// Setup any resources needed for system monitoring
}
@Override
public String getName() {
return "MyIntervalMonitor";
}
@Override
public boolean isEnabled() {
return true;
}
}
Use Cases
Interval monitors are ideal for:
- System resource monitoring
- Memory leak detection
- Performance baseline establishment
- Capacity planning data
- Health check implementations
Deployment via Extension
Extensions can declare all three monitor types in their META-INF/MANIFEST.MF
file:
Manifest-Version: 1.0
Built-Date: 2022-03-22 11:46:12
version: "1.0.0.176"
id: "58110B5E-E7CB-47AF-8E80D70DDD80C46F"
name: "Argus Monitor"
description: "Argus Monitor is a tool to show the current state of your system."
start-bundles: false
category: "Monitor"
author: "Michael Offner"
release-type: server
monitor: "[{'name':'ArgusMonitorActionMonitor','type':'action','class':'ch.rasia.extension.argus.monitor.ActionMonitorImpl','bundleName':'argus.monitor','bundleVersion':'1.0.0.176'},
{'name':'ArgusMonitorIntervalMonitor','type':'interval','class':'ch.rasia.extension.argus.monitor.IntervalMonitorImpl','bundleName':'argus.monitor','bundleVersion':'1.0.0.176'},
{'name':'ArgusMonitorRequestMonitor','type':'request','class':'ch.rasia.extension.argus.monitor.RequestMonitorImpl','bundleName':'argus.monitor','bundleVersion':'1.0.0.176'}]"
Accessing Monitor Data from CFML
Lucee provides CFML functions to interact with monitor data programmatically:
// Get action monitor data
actionData = getMonitorData("action", {
startDate: dateAdd("d", -7, now()),
endDate: now(),
type: "query"
});
// Get request monitor data
requestData = getMonitorData("request", {
startDate: dateAdd("h", -1, now()),
endDate: now(),
errorOnly: true
});
// Get interval monitor data
systemData = getMonitorData("interval", {
startDate: dateAdd("h", -2, now()),
endDate: now()
});
Security Considerations
When implementing hooks and monitors, consider these security aspects:
- Class Loading: Ensure only trusted classes are loaded
- Configuration: Protect sensitive configuration data
- Monitor Data: Be careful about exposing sensitive data through monitor interfaces
- Resource Access: Monitors have access to request data and system information
- Performance Impact: Keep monitor logic lightweight to avoid performance degradation
Troubleshooting
Common Issues
Hook Not Loading
- Verify class name and path are correct
- Check bundle/Maven dependencies are available
- Review Lucee logs for error messages
Monitor Not Triggering
- Ensure monitoring is enabled in
.CFConfig.json
- Check that the monitor interface is implemented correctly
- Verify the extension manifest syntax is valid
Performance Issues
- Keep monitor logic lightweight
- Use asynchronous processing for expensive operations
- Implement proper error handling to prevent monitor failures
Debugging
Enable detailed logging to troubleshoot issues:
{
"loggers": {
"startup": {
"level": "DEBUG"
},
"monitor": {
"level": "INFO"
}
}
}
Future Development
Both the hook and monitoring systems continue to evolve with plans for:
- Additional Hook Points: More lifecycle events for hook integration
- Real-time Monitor Streaming: WebSocket-based real-time monitor data
- Integrated Dashboards: Combined view of hook status and monitor data
- Alerting Framework: Built-in alerting based on monitor thresholds
- Configuration Templates: Pre-built configurations for common use cases
Your feedback and suggestions are welcome to help shape the future of Lucee's extension and monitoring capabilities.