Porting Lucee CFML's SystemOutput() Function Over To Adobe ColdFusion
In my previous post on dynamically extending a distributed lock in Redis, Paolo Olocco asked about some Lucee CFML / Adobe ColdFusion compatibility issues; one of which was how to implement the SystemOutput()
function. At InVision, writing to the standard out and error streams is a very important action: we use SystemOutput()
to write all of our logs to the these streams where they are then aggregated and shipped off to Loggly. As such, I thought it would be a fun code kata to try and port the SystemOutput()
function over to Adobe ColdFusion.
The SystemOutput()
function is fairly light-weight: it takes a value and writes it to either the standard out
or the standard err
stream. Then, it optionally adds a newline character. The inclusiong of a newline character is critical as log aggregation translates one entire line of output to one log-entry.
In Lucee CFML, complex objects get serialized automatically. For this, I am going to use the serializeJson()
function. Lucee CFML will also replace a few special tokens into the serialized value: <print-stack-trace>
and <hash-code>
. I am not porting this "magic string" functionality over as I've never used it and I don't fully understand what the point of it is.
With that said, here's my attempt at an Adobe ColdFusion compatible SystemOutput()
function:
<cfscript>
ADD_NEW_LINE = true;
USE_ERROR_STREAM = true;
// Simple values can be logged.
SystemOutput( "Hello world", ADD_NEW_LINE );
SystemOutput( true, ADD_NEW_LINE );
SystemOutput( 1234, ADD_NEW_LINE );
// Complex values can be logged and will be serialized automatically as JSON.
SystemOutput(
{
id: 4,
name: "Sarah",
phone: "212-555-1234"
},
ADD_NEW_LINE
);
try {
throw( type = "OhNoYouDidnt!" );
} catch ( any error ) {
// Values can be logged to the error stream.
SystemOutput( "Ooops: #error.type#", ADD_NEW_LINE, USE_ERROR_STREAM );
}
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
/**
* I port (most of) the SystemOutput() functionality over to Adobe ColdFusion. If the
* passed-in object is a complex value, it will be serialized as JSON.
*
* CAUTION: Does not support "<print-stack-trace>" replacement.
*
* @obj I am the value being logged.
* @addNewLine I determine if a newline character will be appended.
* @doErrorStream I determine if the error output should be used.
*/
public void function SystemOutput(
required any obj,
boolean addNewLine = false,
boolean doErrorStream = false
) {
var SystemClass = createObject( "java", "java.lang.System" );
var stream = ( doErrorStream )
? SystemClass.err
: SystemClass.out
;
var streamMethod = ( addNewLine )
? "println"
: "print"
;
var serializedValue = ( isSimpleValue( obj ) )
? toString( obj )
: serializeJson( obj )
;
invoke( stream, streamMethod, [ serializedValue ] );
}
</cfscript>
Here, we're ultimately dipping down into the Java layer and grabbing either the standard out
or standard err
streams from the java.lang.System
Class. Then, we're either invoking the print()
function by default; or, the println()
function when we need to include a new line.
ASIDE: I love that we can use
invoke()
to dynamically execute methods on Java objects - ColdFusion is so freaking flexible!
And, when we run this CFML code with Adobe ColdFusion 2018 in CommandBox, we get the following server log --follow
output:
Given the fact that more applications are moving to a containerized architecture; and, that many containerized applications are aggregating logs using the server's output stream, I'm be shocked if Adobe ColdFusion didn't add its own native SystemOutput()
function in a future release. But, until that happens, hopefully this port might help.
Epilogue: Linking Log Files to Output Streams
Back when InVision was running on Adobe ColdFusion 10, we used to have our Dockerfile create "symbolic links" (aka, symlinks) between the server's output stream(s) and ColdFusion's log files. This way, anything that was logged either implicitly by the runtime or explicitly using writeLog()
would automatically be piped to the output stream (where it was then consumed by our log aggregation).
This approach worked, but it was always kind of janky. For one, it felt a bit too "magical". And, you could never open the logs up in the Adobe ColdFusion Administrator's log-viewer since the files weren't really there - they were just symlinked to streams. As such, even before we switched to Lucee CFML we ended up replacing the symlinks approach with a SystemOutput()
port similar to what I have in this post.
Want to use code from this post? Check out the license.
Reader Comments
Ben
It would be great if we could remove the need for:
Argument
Is there anyway we can detect an error automatically, inside your customised SystemOutput?
I have found an interesting Java class:
Apparently, this can detect whether an error has been thrown globally!
@Charles,
It's an interesting idea. I can tell you that from my standpoint, where I am using the output streams for log aggregation, the type of stream -
out
vserr
- don't actually make a difference. This is because we are using "structured logging", where we're outputting JSON object that contain the "level" of the log entry. So, if we output something like:... it doesn't matter if it is written to the
out
stream or theerr
stream - it just gets slurped-up and sent off to Loggly where it gets indexed based onlevel
.I say all this only to say that trying to be "too clever" inside the
SystemOutput()
function may be overkill for how people end up using it. Of course, I can only speak from my own usage perspective.Thanks so much Ben, it's really interesting.
We hope Adobe introduces this feature
@All,
So, it turns out that the
CFDump
tag and thewriteDump()
function in Adobe ColdFusion can target the standard out stream instead of writing to the browser:www.bennadel.com/blog/4150-writing-to-the-standard-out-console-using-writedump-in-adobe-coldfusion-2021.htm
While this doesn't duplicate what
systemOutput()
can do, it will certainly make debugging my Adobe ColdFusion updates a bit easier 💪