Skip to content

Commit 683d62f

Browse files
committed
add logging from java recipe
1 parent b4475cc commit 683d62f

File tree

1 file changed

+247
-0
lines changed

1 file changed

+247
-0
lines changed
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
<!--
2+
{
3+
"title": "Java Classes Using Lucee's Logging",
4+
"id": "java-using-lucee-logging",
5+
"since": "7.0",
6+
"categories": ["java", "logging"],
7+
"description": "How to make Java classes log through Lucee's logging system",
8+
"keywords": [
9+
"java",
10+
"logging",
11+
"slf4j",
12+
"log4j",
13+
"lucee.commons.io.log.Log"
14+
],
15+
"related": [
16+
"logging",
17+
"java-class-interaction",
18+
"java-libraries",
19+
"tag-log",
20+
"function-writelog"
21+
]
22+
}
23+
-->
24+
25+
# Java Classes Using Lucee's Logging
26+
27+
You've moved some heavy processing into a Java class - maybe PDF generation, image manipulation, or a payment gateway integration. It works great, but when something goes wrong, you're flying blind. You want logging, just like you'd use `writeLog()` in CFML.
28+
29+
You search online and every Java tutorial says "use SLF4J". So you add logging calls, deploy to Lucee, and... nothing. Your logs vanish into thin air.
30+
31+
This recipe shows how to get your Java classes logging alongside your CFML code, using Lucee's built-in logging system.
32+
33+
## The Problem with SLF4J
34+
35+
If you've looked into Java logging, you've probably encountered [SLF4J](https://www.slf4j.org/) (Simple Logging Facade for Java). It's the standard way Java libraries handle logging - instead of choosing a specific logging implementation, they log through SLF4J, and the application decides where those logs actually go.
36+
37+
Code using SLF4J looks like this:
38+
39+
```java
40+
import org.slf4j.Logger;
41+
import org.slf4j.LoggerFactory;
42+
43+
private static final Logger logger = LoggerFactory.getLogger("payment");
44+
logger.info("Processing payment...");
45+
```
46+
47+
This won't work in Lucee. Lucee bundles something called `slf4j-nop` - the "no operation" binding - which deliberately silences all SLF4J logging. This stops noisy third-party libraries from flooding your logs, but it also means your Java code logs to nowhere.
48+
49+
You could try to fight Lucee's OSGi classloader system and swap in a different SLF4J binding, but there's a much simpler approach.
50+
51+
## The Solution: Pass Lucee's Logger to Java
52+
53+
Instead of trying to configure Java's logging infrastructure, just pass Lucee's logger directly to your Java class. Think of it like dependency injection - your CFML code creates the logger and hands it to Java.
54+
55+
### Step 1: Write the Java Class
56+
57+
Here's a simple Java class that accepts a Lucee logger. If you're new to Java, don't worry - this is about as simple as it gets:
58+
59+
```java
60+
package com.mycompany.utils;
61+
62+
import lucee.commons.io.log.Log;
63+
64+
public class PaymentProcessor {
65+
66+
private final Log logger;
67+
68+
// Constructor - receives the logger from CFML
69+
public PaymentProcessor( Log logger ) {
70+
this.logger = logger;
71+
}
72+
73+
public boolean processPayment( String customerId, double amount ) {
74+
// Log methods take two arguments: a "source" name and the message
75+
// The source helps you identify where the log came from
76+
logger.debug( "PaymentProcessor", "Starting payment for: " + customerId );
77+
logger.info( "PaymentProcessor", "Processing $" + amount + " for " + customerId );
78+
79+
if ( amount > 10000 ) {
80+
logger.warn( "PaymentProcessor", "Large transaction: $" + amount );
81+
}
82+
83+
return true;
84+
}
85+
}
86+
```
87+
88+
The key things to notice:
89+
90+
- **`import lucee.commons.io.log.Log`** - This is Lucee's logging interface
91+
- **`private final Log logger`** - Store the logger as a class field (like a component variable)
92+
- **Constructor takes `Log logger`** - CFML will pass this when creating the object
93+
- **`logger.info( source, message )`** - Similar to `writeLog( text=message, log="payment" )`
94+
95+
### Step 2: Build with Maven
96+
97+
You'll need Maven to compile your Java class. If you haven't used Maven before, it's Java's equivalent of npm or CommandBox - it manages dependencies and builds your code.
98+
99+
Create a `pom.xml` file in your project folder:
100+
101+
```xml
102+
<?xml version="1.0" encoding="UTF-8"?>
103+
<project xmlns="http://maven.apache.org/POM/4.0.0"
104+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
105+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
106+
<modelVersion>4.0.0</modelVersion>
107+
108+
<groupId>com.mycompany.utils</groupId>
109+
<artifactId>paymentprocessor</artifactId>
110+
<version>1.0.0</version>
111+
112+
<properties>
113+
<maven.compiler.source>11</maven.compiler.source>
114+
<maven.compiler.target>11</maven.compiler.target>
115+
</properties>
116+
117+
<dependencies>
118+
<!-- Use your target Lucee version - see https://mvnrepository.com/artifact/org.lucee/lucee -->
119+
<dependency>
120+
<groupId>org.lucee</groupId>
121+
<artifactId>lucee</artifactId>
122+
<version>7.0.1.100</version>
123+
<scope>provided</scope>
124+
</dependency>
125+
</dependencies>
126+
</project>
127+
```
128+
129+
The `<scope>provided</scope>` bit is important - it tells Maven "I need Lucee to compile against, but don't bundle it in my jar". Lucee is already running when your code executes.
130+
131+
Build your jar with:
132+
133+
```bash
134+
mvn clean package
135+
```
136+
137+
This creates `target/paymentprocessor-1.0.0.jar`.
138+
139+
### Step 3: Configure the Logger in Lucee
140+
141+
Before you can use a named logger, it needs to exist in Lucee. You've probably done this before for CFML logging.
142+
143+
Add it to your `.CFConfig.json`:
144+
145+
```json
146+
{
147+
"loggers": {
148+
"payment": {
149+
"appender": "console",
150+
"layout": "classic",
151+
"level": "debug"
152+
}
153+
}
154+
}
155+
```
156+
157+
Or create it through Lucee Admin under Settings > Logging.
158+
159+
### Step 4: Call from CFML
160+
161+
Now the fun part - wire it all together:
162+
163+
```cfml
164+
<cfscript>
165+
// Get the logger you configured in Lucee
166+
paymentLog = getPageContext().getConfig().getLog( "payment" );
167+
168+
// Load your jar and pass the logger to the constructor
169+
jarPath = getDirectoryFromPath( getCurrentTemplatePath() ) & "lib/paymentprocessor-1.0.0.jar";
170+
processor = createObject( "java", "com.mycompany.utils.PaymentProcessor", jarPath )
171+
.init( paymentLog );
172+
173+
// Use your Java class - all logging goes through Lucee
174+
processor.processPayment( "CUST-12345", 99.95 );
175+
</cfscript>
176+
```
177+
178+
Your Java logs now appear in the same place as your CFML logs, with the same formatting and log levels. No separate log files to check, no Java logging configuration to maintain.
179+
180+
## Log Levels
181+
182+
The log levels work just like `writeLog()` in CFML. The [`lucee.commons.io.log.Log`](https://www.javadoc.io/static/org.lucee/lucee/7.0.1.100/lucee/commons/io/log/Log.html) interface is thread-safe and provides:
183+
184+
| Java Method | CFML Equivalent |
185+
|--------------------------------|----------------------------------------------|
186+
| `logger.trace( app, message )` | `writeLog( type="trace", ... )` |
187+
| `logger.debug( app, message )` | `writeLog( type="debug", ... )` |
188+
| `logger.info( app, message )` | `writeLog( type="information", ... )` |
189+
| `logger.warn( app, message )` | `writeLog( type="warning", ... )` |
190+
| `logger.error( app, message )` | `writeLog( type="error", ... )` |
191+
| `logger.fatal( app, message )` | `writeLog( type="fatal", ... )` |
192+
193+
### Logging Exceptions
194+
195+
In Java, you'll often want to log exceptions with their stack traces. The Log interface handles this for you:
196+
197+
```java
198+
try {
199+
// something risky
200+
} catch ( Exception e ) {
201+
// Log with a message and the exception (includes stack trace)
202+
logger.error( "PaymentProcessor", "Payment failed", e );
203+
// Or just the exception
204+
logger.error( "PaymentProcessor", e );
205+
throw e;
206+
}
207+
```
208+
209+
## Handling Missing Loggers
210+
211+
If you request a logger that doesn't exist, `getLog()` throws an exception. This is a good thing - you want to know immediately if your logging config is wrong, not discover missing logs in production.
212+
213+
If you need to support different environments where the logger might not be configured, check first:
214+
215+
```cfml
216+
config = getPageContext().getConfig();
217+
logNames = config.getLogNames();
218+
219+
if ( arrayContains( logNames, "payment" ) ) {
220+
paymentLog = config.getLog( "payment" );
221+
} else {
222+
// Fall back to application log (always exists)
223+
paymentLog = config.getLog( "application" );
224+
}
225+
```
226+
227+
## Multiple Loggers
228+
229+
Just like you might use different log files in CFML for different purposes, you can pass multiple loggers to your Java class:
230+
231+
```cfml
232+
config = getPageContext().getConfig();
233+
auditLog = config.getLog( "audit" );
234+
errorLog = config.getLog( "error" );
235+
236+
processor = createObject( "java", "com.mycompany.Processor", jarPath )
237+
.init( auditLog, errorLog );
238+
```
239+
240+
Your Java constructor would then accept both:
241+
242+
```java
243+
public Processor( Log auditLog, Log errorLog ) {
244+
this.auditLog = auditLog;
245+
this.errorLog = errorLog;
246+
}
247+
```

0 commit comments

Comments
 (0)