Issues Defining Loggers in Application.cfc

Hi all,

We’re running into a persistent issue with Lucee on Azure and would appreciate any insights.

The Problem: Almost daily, we lose all Lucee configuration data and logs. This includes critical settings like our database connection, which causes the site to go down until we reconfigure.

Our Workaround: To keep the site running, we moved the DB connection setup into the application.cfc file itself—this works well and survives the resets.

What We’re Trying to Do: To diagnose the root cause, we wanted to enable persistent logging. We found that Lucee supports logging directly to a database, which is perfect for our needs.

We successfully configured this via the Lucee Admin interface, that config will get lost during the next reset as well as the log files.

What We Tried: We attempted to define the DB logger in:

  • CFConfig.json
  • Application.cfc (similar to how we define the DB connection)
    • an example of the config used
<cfset this.loggers["dberrorlog"] = {
   appender: "datasource",
   appenderArguments: {
   	datasource: "db name here",
   	tablename: "table name here",
   },
   level: "error"
}> 

And then we call cflog like this

<cflog log="dberrorlog" type="error" exception="#arguments.error#">

But both approaches result in this error

What We Need Help With:

  • Has anyone successfully configured DB logging in Lucee using CFConfig.json or Application.cfc?
  • Are there best practices for persisting Lucee config in Azure environments?
  • Any ideas why the config and logs are being wiped daily?

Thanks in advance for any help or suggestions!

I have seen this randomly locally, my theory is that sometimes if there’s an error with the config, it can be somehow corrupted and gets discarded / reset.

From memory, it’s happened whilst doing some torture debugging/stress testing config stuff locally with Lucee, but that should never ever be happening.

Almost daily… is there any pattern to the problem?

Do you have code changing the Lucee configuration, either via configimport() or via the cfadmin tag?

By logs you mean the configured logs, rather than the actual files?

I’m not sure where you got this.loggers from (edit: that’s the key name in CFConfig.json), for Application.cfc it’s this.logs, you can verify your runtime configuration by dumping out getApplicationSettings()

Do you see anything in the out.log or err.log files under \tomcat\lucee-server\context\logs, they are the fallback logs when stuff goes badly wrong or before the normal logs are initialized

Also, what version of Lucee are you running?

Which Azure service are you using to run your Lucee instance(s)? VM or container?

Starting with the easy questions, Lucee version is 5.4.6.9 and I am running it in a Azure Web App with the following configuration:

  • Stack: Java 11
  • Java Web Server: Apache Tomcat 9.0.54

And we only run one instance of the site.

Resource Usage

We’ve recently started digging deeper into some stability issues, after pushing a DB fix to prevent site crashes. I’ve been monitoring CPU and memory usage and, as shown in the screenshots below, the system is under very little stress—averaging around 50% memory usage and low CPU utilization:


To validate the metrics, I ran a basic test by hitting the site a few times per second from my local machine. This briefly maxed out the CPU for about a minute, but the site remained stable. The only noticeable impact was a slight increase in response times, which I suspect was mitigated by the Azure load balancer.

Logging Issues

I have also tried this.logs in Application.cfc but this instead of an error it gave me a white screen. I was able to find the below error in the tomcat logs

"ERROR","http-nio-80-exec-2","07/04/2025","16:16:08","controller","java.lang.NullPointerException;java.lang.NullPointerException
	at lucee.commons.io.log.log4j2.Log4j2Engine.layoutClassDefintion(Log4j2Engine.java:154)
	at lucee.runtime.listener.ApplicationContextSupport.initLog(ApplicationContextSupport.java:335)
	at lucee.runtime.listener.ModernApplicationContext.initLog(ModernApplicationContext.java:1761)
	at lucee.runtime.listener.ModernApplicationContext.getLog(ModernApplicationContext.java:1735)
	at lucee.runtime.PageContextImpl.getLog(PageContextImpl.java:3775)
	at lucee.runtime.engine.ThreadLocalPageContext.getLog(ThreadLocalPageContext.java:173)
	at lucee.runtime.exp.ExceptionHandler.log(ExceptionHandler.java:37)
	at lucee.runtime.PageContextImpl.handlePageException(PageContextImpl.java:2057)
	at lucee.runtime.PageContextImpl.handlePageException(PageContextImpl.java:2030)
	at lucee.runtime.listener.ModernAppListener.onError(ModernAppListener.java:439)
	at lucee.runtime.listener.MixedAppListener.onError(MixedAppListener.java:138)
	at lucee.runtime.PageContextImpl.execute(PageContextImpl.java:2522)
	at lucee.runtime.PageContextImpl._execute(PageContextImpl.java:2479)
	at lucee.runtime.PageContextImpl.executeCFML(PageContextImpl.java:2450)
	at lucee.runtime.engine.Request.exe(Request.java:45)
	at lucee.runtime.engine.CFMLEngineImpl._service(CFMLEngineImpl.java:1215)
	at lucee.runtime.engine.CFMLEngineImpl.serviceCFML(CFMLEngineImpl.java:1161)
	at lucee.loader.engine.CFMLEngineWrapper.serviceCFML(CFMLEngineWrapper.java:97)
	at lucee.loader.servlet.CFMLServlet.service(CFMLServlet.java:51)

Which I didn’t really understand why it was a null pointer exception?!

Tomact Logs

Good shout for checking this, I don’t get an out.log or err.log I instead get catalina.hash.year-month-day.log. I have checked them on a known failure day but it was what looks like normal start up logs. There are two common warnings

18-Jun-2025 02:49:05.411 WARNING [main] org.apache.catalina.startup.ClassLoaderFactory.validateFile Problem with directory [C:\home\tomcat\lib], exists: [false], isDirectory: [false], canRead: [false]
18-Jun-2025 02:49:05.426 WARNING [main] org.apache.catalina.startup.ClassLoaderFactory.validateFile Problem with directory [C:\home\site\libs], exists: [false], isDirectory: [false], canRead: [false]

but as the server starts up I didn’t think it was an issue.

@Zackster

To address this issue, I’ve implemented a new log management system using a scheduled task. This task runs at regular intervals and checks whether the log file already exists in Azure. If it does, the task downloads the existing file, appends the new logs, and re-uploads it, overwriting the previous version. Afterward, it deletes the local log file from the Lucee server to ensure that only new data is processed during the next run.

If the log file doesn’t exist in Azure, the task simply uploads the new log file and deletes it locally. Additionally, I’ve added a measure to prevent Lucee’s automatic backup of log files, so the system only handles .log files and manages its own backups. This prevents large files from being transferred during each run and keeps the process efficient.

<cfcomponent>
  <cffunction name="getLogNames" return="array" access="public">
    <cfset local.logNames = directoryList(getLogFilePath(), false, "name", function(fileName) {return arguments.fileName.hasSuffix(".log")})>
    <cfreturn local.logNames>
  </cffunction>

  <cffunction name="getLogFilePath" return="string">
    <cfargument name="fileName" type="string" default="">
    <cfset local.fileName = arguments.fileName.Len() eq 0 ? "" : "/" & arguments.fileName>
    <cfreturn expandPath("#GetDirectoryFromPath(GetCurrentTemplatePath())#../../../../WEB-INF/lucee/logs" & local.fileName) >
  </cffunction>

  <cffunction name="backupAllLogs" access="remote" returnType="string">
    <cfargument name="threadData" type="struct">
    <cfset local.logNames = getLogNames()>
    <cfset arguments.threadData.progressData[0].name = "Logs to backup">
    <cfset arguments.threadData.progressData[0].current = 1>
    <cfset arguments.threadData.progressData[0].total = "#arrayLen(local.logNames)#">

    <cfdump  var="Starting log backup #getLogFilePath()# #arrayLen(local.logNames)#" output="console">
    <cfif arrayLen(local.logNames) gt 0>
      <cfloop array="#local.logNames#" item="local.logName">
        <cfset backupLog(replace(local.logName, ".log", ""))>
        <cfdump  var="Backed up #local.logName#" output="console">
        <cfset arguments.threadData.progressData[0].current++>
        <cfif arguments.threadData.abortThread>
          <cfreturn "aborted">
        </cfif>
      </cfloop>
    </cfif> 
    <cfreturn "success">
  </cffunction>

  <cffunction name="backupLog" access="remote">
    <!---  just the file name, no extension  --->
    <cfargument name="logName" type="string">

    <cfset local.logPath = getLogFilePath("#arguments.logName#.log")>
    
    <!--- if the file doesn't exist do nothing--->
    <cfif !fileExists(local.logPath)>
      <cfreturn>
    </cfif>
    <!--- if the file exists but has no data, do nothing --->
    <cfif fileInfo(local.logPath).size eq 0>
      <cfreturn>
    </cfif>

    <cfset local.blobName = "/logs/#arguments.logName#.log">
    <cfset local.blobLogName = "#arguments.logName#-blob.log">
    <cfset local.blobLogPath = getLogFilePath(local.blobLogName)>
    <cfset local.blobExists = application.BlobStorageUtilities.blobExists(local.blobName)>

    <cfif local.blobExists>
      <cfset local.blobLogs = application.BlobStorageUtilities.downloadBlob(local.blobName, local.blobLogPath)>

      <cfset fileAppend(local.blobLogPath, fileRead(local.logPath))>

      <!---   if the file is over the lucee log file limit (-1000 bytes to prevent lucee going over and auto renaming the file)      --->
      <cfif getFileInfo(local.blobLogPath).size gt 9999000>
				<cfset local.archiveBlobLogFilename = application.utilities.makeUniqueBlobFilename("/logs", "#arguments.logName#.log")>
        <cfset application.BlobStorageUtilities.uploadBlob("/logs/#local.archiveBlobLogFilename#", local.blobLogPath)>
        <cfset application.BlobStorageUtilities.deleteBlob(local.blobName)>
      <cfelse>
        <cfset application.BlobStorageUtilities.uploadBlob(local.blobName, local.blobLogPath)>
      </cfif>

      <cfset fileDelete(local.blobLogPath)>
      <cfset fileDelete(local.logPath)> 
    <cfelse>
      <cfset application.BlobStorageUtilities.uploadBlob(local.blobName, local.logPath)>
      <cfset fileDelete(local.logPath)> 
    </cfif>
  </cffunction>
</cfcomponent>

The current issue I’m facing is that Lucee or possibly the underlying Java process is holding onto the log files, which causes errors when I attempt to delete them (error output below). I suspect this is related to the garbage collector not flushing file I/O properly after reading the files.

To address this, I’ve tried several approaches. One approach was to wait until all uploads are complete before deleting the files in a separate loop after backupLog is called. I also switched from using fileRead and fileAppend to manually opening, reading, appending, and closing the files to ensure proper handling, but this didn’t resolve the issue.

Interestingly, when I generate log files using a Python script, my deletion logic works without any problems. This leads me to believe the issue is specifically tied to cflog in Lucee.

Have you encountered this issue before? Is there a reliable way to flush the file I/O buffer or explicitly trigger garbage collection to release the file handles?

As always, I appreciate any help
Thanks in advance!

(running Lucee 5.4.6.9)

"Severity","ThreadID","Date","Time","Application","Message"
"ERROR","cfthread-13","07/15/2025","16:42:26","cfthread-13","Can't delete file [WEB-INF\lucee\logs\application.log];lucee.runtime.exp.NativeException: Can't delete file [WEB-INF\lucee\logs\application.log]
	at lucee.commons.io.res.type.file.FileResource.remove(FileResource.java:305)
	at lucee.runtime.functions.file.FileDelete.call(FileDelete.java:37)
	at cfc.com.arup.invest.logutility_cfc$cf.udfCall(/cfc/com/arup/invest/logUtility.cfc:70)
	at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:112)
	at lucee.runtime.type.UDFImpl._call(UDFImpl.java:358)
	at lucee.runtime.type.UDFImpl.call(UDFImpl.java:223)
	at lucee.runtime.type.scope.UndefinedImpl.call(UndefinedImpl.java:786)
	at lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:787)
	at lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1776)
	at cfc.com.arup.invest.logutility_cfc$cf.udfCall(/cfc/com/arup/invest/logUtility.cfc:24)
	at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:112)
	at lucee.runtime.type.UDFImpl._call(UDFImpl.java:358)
	at lucee.runtime.type.UDFImpl.callWithNamedValues(UDFImpl.java:213)
	at lucee.runtime.ComponentImpl._call(ComponentImpl.java:665)
	at lucee.runtime.ComponentImpl._call(ComponentImpl.java:586)
	at lucee.runtime.ComponentImpl.callWithNamedValues(ComponentImpl.java:1947)
	at lucee.runtime.tag.Invoke.doComponent(Invoke.java:209)
	at lucee.runtime.tag.Invoke.doEndTag(Invoke.java:186)
	at cfc.com.arup.invest.thread_cfc$cf.udfCall(/cfc/com/arup/invest/thread.cfc:156)
	at lucee.runtime.type.UDFImpl.implementation(UDFImpl.java:112)
	at lucee.runtime.type.UDFImpl._call(UDFImpl.java:358)
	at lucee.runtime.type.UDFImpl.call(UDFImpl.java:223)
	at lucee.runtime.type.scope.UndefinedImpl.call(UndefinedImpl.java:786)
	at lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:787)
	at lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1776)
	at cfc.com.arup.invest.thread_cfc$cf.threadCall(/cfc/com/arup/invest/thread.cfc:97)
	at lucee.runtime.thread.ChildThreadImpl.execute(ChildThreadImpl.java:203)
	at lucee.runtime.thread.ChildThreadImpl.run(ChildThreadImpl.java:145)
Caused by: java.io.IOException: Can't delete file [WEB-INF\lucee\logs\application.log]
	... 28 more
"