Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions system/logging/LogBox.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,11 @@ component accessors="true" {
var rootConfig = variables.config.getRoot();
// Create Root Logger
var args = {
category : "ROOT",
levelMin : rootConfig.levelMin,
levelMax : rootConfig.levelMax,
appenders : getAppendersMap( rootConfig.appenders )
category : "ROOT",
levelMin : rootConfig.levelMin,
levelMax : rootConfig.levelMax,
appenders : getAppendersMap( rootConfig.appenders ),
allowSerializingComplexObjects : variables.config.getAllowSerializingComplexObjects()
};

// Save in Registry
Expand Down Expand Up @@ -257,9 +258,10 @@ component accessors="true" {
root = locateCategoryParentLogger( arguments.category );
// Build it out as per Root logger
args = {
category : arguments.category,
levelMin : root.getLevelMin(),
levelMax : root.getLevelMax()
category : arguments.category,
levelMin : root.getLevelMin(),
levelMax : root.getLevelMax(),
allowSerializingComplexObjects : variables.config.getAllowSerializingComplexObjects()
};
}

Expand Down
72 changes: 66 additions & 6 deletions system/logging/Logger.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ component accessors="true" {
*/
property name="levelMax";

/**
* Allow Serializing Complex Objects
*/
property
name ="allowSerializingComplexObjects"
type ="boolean"
default="true";

// The log levels enum as a public property
this.logLevels = new coldbox.system.logging.LogLevels();

Expand All @@ -44,14 +52,16 @@ component accessors="true" {
*/
function init(
required category,
numeric levelMin = 0,
numeric levelMax = 4,
struct appenders = {}
numeric levelMin = 0,
numeric levelMax = 4,
struct appenders = {},
boolean allowSerializingComplexObjects = true
){
// Save Properties
variables.rootLogger = "";
variables.category = arguments.category;
variables.appenders = arguments.appenders;
variables.rootLogger = "";
variables.category = arguments.category;
variables.appenders = arguments.appenders;
variables.allowSerializingComplexObjects = arguments.allowSerializingComplexObjects;

// Logger Logging Level defaults, which is wideeeee open!
variables.levelMin = arguments.levelMin;
Expand Down Expand Up @@ -326,6 +336,14 @@ component accessors="true" {
required severity,
extraInfo = ""
){
if ( !variables.allowSerializingComplexObjects && checkForComplexObjects( arguments.extraInfo ) ) {
throw(
message = "Attempted to log a complex object in the extraInfo parameter, but it is disallowed.",
detail = "Log Message: #arguments.message#; Log Severity: #arguments.severity#",
type = "Logger.SerializingComplexObjectException"
);
}

var target = this;

// Verify severity, if invalid, default to INFO
Expand Down Expand Up @@ -456,4 +474,46 @@ component accessors="true" {
return canLog( this.logLevels.DEBUG );
}

private boolean function checkForComplexObjects( required any value ){
Copy link
Member

@lmajano lmajano Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

document the function, no excuses now, use chatgpt

if ( isNull( arguments.value ) ) {
return false;
}

if ( isSimpleValue( arguments.value ) ) {
return false;
}

// If the object has a $toString() method, then we consider it safe
if ( isObject( arguments.value ) AND structKeyExists( arguments.value, "$toString" ) ) {
return false;
}

// CFML Exceptions are safe
if ( isCFMLException( arguments.value ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the cfml, this is now. boxlang + cfml engine, use abstractions: isException()

return false;
}

if ( isArray( arguments.value ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In all reality, if we are going to NOT allow anything complex, I would say don't even check for here down, why go through the trouble of iterate through each of the values, this is time consuming, error proned and just slow.

return arraySome( arguments.value, function( v ){
return checkForComplexObjects( v );
} );
}

if ( isStruct( arguments.value ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

return structSome( arguments.value, function( k, v ){
return checkForComplexObjects( v );
Comment on lines +498 to +504
Copy link
Preview

Copilot AI Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recursive check for complex objects could cause performance issues with deeply nested or large data structures. Consider adding a depth limit or size limit to prevent excessive recursion that could impact logging performance.

Suggested change
return checkForComplexObjects( v );
} );
}
if ( isStruct( arguments.value ) ) {
return structSome( arguments.value, function( k, v ){
return checkForComplexObjects( v );
return checkForComplexObjects( v, depth + 1 );
} );
}
if ( isStruct( arguments.value ) ) {
return structSome( arguments.value, function( k, v ){
return checkForComplexObjects( v, depth + 1 );

Copilot uses AI. Check for mistakes.

} );
}

return true;
}

private boolean function isCFMLException( required any value ){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change method name to isException and document it

return ( isObject( arguments.value ) || isStruct( arguments.value ) ) && (
structKeyExists( arguments.value, "stacktrace" ) &&
structKeyExists( arguments.value, "message" ) &&
structKeyExists( arguments.value, "detail" )
);
}

}
12 changes: 12 additions & 0 deletions system/logging/config/LogBoxConfig.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ component accessors="true" {
*/
property name="rootLogger" type="struct";

/**
* Flag to allow serializing complex objects in `extraInfo`.
*/
property
name ="allowSerializingComplexObjects"
type ="boolean"
default="true";

// The log levels enum as a public property
this.logLevels = new coldbox.system.logging.LogLevels();
// Startup the configuration
Expand Down Expand Up @@ -128,6 +136,10 @@ component accessors="true" {
OFF( argumentCollection = getUtil().arrayToStruct( logBoxDSL.off ) );
}

if ( structKeyExists( logBoxDSL, "allowSerializingComplexObjects" ) ) {
variables.allowSerializingComplexObjects = logBoxDSL.allowSerializingComplexObjects;
}

return this;
}

Expand Down
63 changes: 63 additions & 0 deletions tests/specs/logging/LoggerTest.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,69 @@
expect( mockAppender.$callLog().logmessage ).toBeEmpty();
} );
} );

describe( "can control serializing complex objects", function(){
beforeEach( function( currentSpec ){
// MockAppender
mockAppender = createStub()
.$( "getName", "MockAppender" )
.$( "isInitialized", true )
.$( "canLog", true )
.$( "getProperty", false )
.$( "logmessage" );
logger.addAppender( mockAppender );
} );

afterEach( function(){
logger.setAllowSerializingComplexObjects( true ); // reset to default
} );

it( "allows serializing complex objects by default", function(){
expect( function(){
logger.logMessage(
"This is a closure message",
"info",
new tests.resources.base1()
);
} ).notToThrow( type = "Logger.SerializingComplexObjectException" );
} );

it( "can disallow serializing complex objects", function(){
logger.setAllowSerializingComplexObjects( false );
expect( function(){
logger.logMessage(
"This is a closure message",
"info",
new tests.resources.base1()
);
} ).toThrow( type = "Logger.SerializingComplexObjectException" );
} );

it( "can find complex objects inside structs", () => {
logger.setAllowSerializingComplexObjects( false );
expect( function(){
logger.logMessage(
"This is a closure message",
"info",
{
"simple" : "value",
"nested" : { "array" : [ "values", "are", "okay" ] }
}
);
} ).notToThrow( type = "Logger.SerializingComplexObjectException" );

expect( function(){
logger.logMessage(
"This is a closure message",
"info",
{
"simple" : "value",
"nested" : { "complex" : new tests.resources.base1() }
}
);
} ).toThrow( type = "Logger.SerializingComplexObjectException" );
} );
} );
} );
}

Expand Down
Loading