Skip to content

Commit 3fb11d5

Browse files
committed
Add new LogBox configuration option to disallow serializing complex objects
A new top level config item named `allowSerializingComplexObjects` controls whether LogBox will attempt to serialize and log out complex objects in `extraInfo` arguments. Defaults to `true`. Turning this to `false` can catch instances where large you using lots of processing time converting objects to XML or JSON unnecessarily. Removing these can increase performance and stability of running applications.
1 parent c3afabf commit 3fb11d5

4 files changed

Lines changed: 150 additions & 13 deletions

File tree

system/logging/LogBox.cfc

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,11 @@ component accessors="true" {
179179
var rootConfig = variables.config.getRoot();
180180
// Create Root Logger
181181
var args = {
182-
category : "ROOT",
183-
levelMin : rootConfig.levelMin,
184-
levelMax : rootConfig.levelMax,
185-
appenders : getAppendersMap( rootConfig.appenders )
182+
category : "ROOT",
183+
levelMin : rootConfig.levelMin,
184+
levelMax : rootConfig.levelMax,
185+
appenders : getAppendersMap( rootConfig.appenders ),
186+
allowSerializingComplexObjects : variables.config.getAllowSerializingComplexObjects()
186187
};
187188

188189
// Save in Registry
@@ -257,9 +258,10 @@ component accessors="true" {
257258
root = locateCategoryParentLogger( arguments.category );
258259
// Build it out as per Root logger
259260
args = {
260-
category : arguments.category,
261-
levelMin : root.getLevelMin(),
262-
levelMax : root.getLevelMax()
261+
category : arguments.category,
262+
levelMin : root.getLevelMin(),
263+
levelMax : root.getLevelMax(),
264+
allowSerializingComplexObjects : variables.config.getAllowSerializingComplexObjects()
263265
};
264266
}
265267

system/logging/Logger.cfc

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ component accessors="true" {
3131
*/
3232
property name="levelMax";
3333

34+
/**
35+
* Allow Serializing Complex Objects
36+
*/
37+
property
38+
name ="allowSerializingComplexObjects"
39+
type ="boolean"
40+
default="true";
41+
3442
// The log levels enum as a public property
3543
this.logLevels = new coldbox.system.logging.LogLevels();
3644

@@ -44,14 +52,16 @@ component accessors="true" {
4452
*/
4553
function init(
4654
required category,
47-
numeric levelMin = 0,
48-
numeric levelMax = 4,
49-
struct appenders = {}
55+
numeric levelMin = 0,
56+
numeric levelMax = 4,
57+
struct appenders = {},
58+
boolean allowSerializingComplexObjects = true
5059
){
5160
// Save Properties
52-
variables.rootLogger = "";
53-
variables.category = arguments.category;
54-
variables.appenders = arguments.appenders;
61+
variables.rootLogger = "";
62+
variables.category = arguments.category;
63+
variables.appenders = arguments.appenders;
64+
variables.allowSerializingComplexObjects = arguments.allowSerializingComplexObjects;
5565

5666
// Logger Logging Level defaults, which is wideeeee open!
5767
variables.levelMin = arguments.levelMin;
@@ -326,6 +336,14 @@ component accessors="true" {
326336
required severity,
327337
extraInfo = ""
328338
){
339+
if ( !variables.allowSerializingComplexObjects && checkForComplexObjects( arguments.extraInfo ) ) {
340+
throw(
341+
message = "Attempted to log a complex object in the extraInfo parameter, but it is disallowed.",
342+
detail = "Log Message: #arguments.message#; Log Severity: #arguments.severity#",
343+
type = "Logger.SerializingComplexObjectException"
344+
);
345+
}
346+
329347
var target = this;
330348

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

477+
private boolean function checkForComplexObjects( required any value ){
478+
if ( isNull( arguments.value ) ) {
479+
return false;
480+
}
481+
482+
if ( isSimpleValue( arguments.value ) ) {
483+
return false;
484+
}
485+
486+
// If the object has a $toString() method, then we consider it safe
487+
if ( isObject( arguments.value ) AND structKeyExists( arguments.value, "$toString" ) ) {
488+
return false;
489+
}
490+
491+
// CFML Exceptions are safe
492+
if ( isCFMLException( arguments.value ) ) {
493+
return false;
494+
}
495+
496+
if ( isArray( arguments.value ) ) {
497+
return arraySome( arguments.value, function( v ){
498+
return checkForComplexObjects( v );
499+
} );
500+
}
501+
502+
if ( isStruct( arguments.value ) ) {
503+
return structSome( arguments.value, function( k, v ){
504+
return checkForComplexObjects( v );
505+
} );
506+
}
507+
508+
return true;
509+
}
510+
511+
private boolean function isCFMLException( required any value ){
512+
return ( isObject( arguments.value ) || isStruct( arguments.value ) ) && (
513+
structKeyExists( arguments.value, "stacktrace" ) &&
514+
structKeyExists( arguments.value, "message" ) &&
515+
structKeyExists( arguments.value, "detail" )
516+
);
517+
}
518+
459519
}

system/logging/config/LogBoxConfig.cfc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ component accessors="true" {
2626
*/
2727
property name="rootLogger" type="struct";
2828

29+
/**
30+
* Flag to allow serializing complex objects in `extraInfo`.
31+
*/
32+
property
33+
name ="allowSerializingComplexObjects"
34+
type ="boolean"
35+
default="true";
36+
2937
// The log levels enum as a public property
3038
this.logLevels = new coldbox.system.logging.LogLevels();
3139
// Startup the configuration
@@ -128,6 +136,10 @@ component accessors="true" {
128136
OFF( argumentCollection = getUtil().arrayToStruct( logBoxDSL.off ) );
129137
}
130138

139+
if ( structKeyExists( logBoxDSL, "allowSerializingComplexObjects" ) ) {
140+
variables.allowSerializingComplexObjects = logBoxDSL.allowSerializingComplexObjects;
141+
}
142+
131143
return this;
132144
}
133145

tests/specs/logging/LoggerTest.cfc

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,69 @@
128128
expect( mockAppender.$callLog().logmessage ).toBeEmpty();
129129
} );
130130
} );
131+
132+
describe( "can control serializing complex objects", function(){
133+
beforeEach( function( currentSpec ){
134+
// MockAppender
135+
mockAppender = createStub()
136+
.$( "getName", "MockAppender" )
137+
.$( "isInitialized", true )
138+
.$( "canLog", true )
139+
.$( "getProperty", false )
140+
.$( "logmessage" );
141+
logger.addAppender( mockAppender );
142+
} );
143+
144+
afterEach( function(){
145+
logger.setAllowSerializingComplexObjects( true ); // reset to default
146+
} );
147+
148+
it( "allows serializing complex objects by default", function(){
149+
expect( function(){
150+
logger.logMessage(
151+
"This is a closure message",
152+
"info",
153+
new tests.resources.base1()
154+
);
155+
} ).notToThrow( type = "Logger.SerializingComplexObjectException" );
156+
} );
157+
158+
it( "can disallow serializing complex objects", function(){
159+
logger.setAllowSerializingComplexObjects( false );
160+
expect( function(){
161+
logger.logMessage(
162+
"This is a closure message",
163+
"info",
164+
new tests.resources.base1()
165+
);
166+
} ).toThrow( type = "Logger.SerializingComplexObjectException" );
167+
} );
168+
169+
it( "can find complex objects inside structs", () => {
170+
logger.setAllowSerializingComplexObjects( false );
171+
expect( function(){
172+
logger.logMessage(
173+
"This is a closure message",
174+
"info",
175+
{
176+
"simple" : "value",
177+
"nested" : { "array" : [ "values", "are", "okay" ] }
178+
}
179+
);
180+
} ).notToThrow( type = "Logger.SerializingComplexObjectException" );
181+
182+
expect( function(){
183+
logger.logMessage(
184+
"This is a closure message",
185+
"info",
186+
{
187+
"simple" : "value",
188+
"nested" : { "complex" : new tests.resources.base1() }
189+
}
190+
);
191+
} ).toThrow( type = "Logger.SerializingComplexObjectException" );
192+
} );
193+
} );
131194
} );
132195
}
133196

0 commit comments

Comments
 (0)