AJS_Logger User-Guide

Pre- and Post-Loggers

Streams give the ability to configure the generation of logging information for a set of LogDefs, but they do not allow you to fine-tune logging behaviour, or even suppress it completely, for a particular LogDef or method when specific conditions prevail. This is the purpose of preLoggers and postLoggers, which form an analogue of Pre- and Post-Validators in the context of AJS_Validator.

Pre- and Post-Loggers are properties of LogDef objects, which refer respectively to user-defined functions (i.e. functions that you write), and their presence in a given LogDef overrides the default streaming-behaviour of the logger-object in question.

That is: if a given LogDef possesses a preLogger property, the logger-object in question calls the preLogger on entry to the methods to which that LogDef applies, and writes nothing to the stream. Equivalently, if a given LogDef possesses a postLogger, then the logger in question will call that function when the method in question returns, instead of writing to its stream object.

In short, the presence of a Pre- and/or Post-Logger in a given LogDef means that it is up to those functions to write explicitly to the stream themselves.

Contents Pre- and Post-Loggers
Pre-/Post-Logger Example
Pre-/Post-Loggers and Suspension/Resumption
Factory Methods

In line with the calling-signatures of Pre- and Post-Validators in the context of AJS_Validator (and those of the onMethodEntry and onMethodExit methods of stream objects), an AJS_Logger object passes preLogger functions the following arguments:

  1. An Args argument that is the arguments array that is passed implicitly to the method when it is invoked.
  2. A MethodOwner object-reference that denotes the object on which the method in question was called.
  3. A MethodName string that represents the name of the method in question.
  4. A reference to the Stream object that the logger in question was passed at its time of instantiation.
  5. A reference to the LogDef object of which the preLogger-function in question is a property.

Logger objects pass postLoggers exactly the same arguments, in the same order, along with a trailing fifth-argument that represents the value returned from the method in question; and Example 16 (a re-presentation of Example 3 from the LogDef Properties section of this guide) gives canonical definitions for these functions.

Note that passing the LogDef reference in the fifth place is of value where the LogDef in question is an anonymous object, and/or is not within the scope of the Pre-/Post-Logger in question, and/or where the Pre-/Post-Logger is shared between two or more LogDefs.

Note also that it is redundant for Pre- and Post-Loggers to return anything, as the corresponding AJS_Logger object will ignore such returns.


 // -- Example 16 -------------------------------------------------------------

 var MyLogDef   =
    {
    MethodNames :                                                                       // Optional.
       [
       "methodName_0",                                                                  // Mandatory.

       "methodName_n"                                                                   // Optional.

       ],

    preLogger   : function (Args, MethodOwner, MethodName, Stream, LogDef)         { }, // Optional.
    postLogger  : function (Args, MethodOwner, MethodName, Stream, LogDef, RtnVal) { }  // Optional.

    };
            

Pre-/Post-Logger Example

Example 17 shows a Pre- and Post-Logger in action. Here, two objects are created, called respectively Obj_A and Obj_B, where Obj_A is 'subordinate' to Obj_B (in other words, and in a more realistic setting, Obj_A performs certain services for Obj_B).

The code also creates two LogDefs, one for Obj_A and one for Obj_B. The Obj_A LogDef simply states the method owner, and leaves the logger to apply logging affixes to the methods of that object, but the Obj_B LogDef is more sophisticated, and defines a preLogger and postLogger.

Those functions use the group and groupEnd methods of the console object, which (when supported upon the platform in question) open and close respectively a nested messaging-block in the console window, such that the output from the calls to console.log that follow the console.group call are indented in that window. The code then creates a logger object, and uses that to apply the two LogDefs.

Subsequently, when Obj_B.someMethod is called, the preLogger for that object executes, calling the console.group method. The body of someMethod then calls Obj_A's two methods, where the default stream logs those calls to the console window in the fashion demonstrated in other examples in this guide. When the execution thread returns to the body of Obj_B.someMethod, that method returns, causing the logger to execute the postLogger for that object, which calls console.groupEnd. This terminates the output-indentation in the console window, and also logs the exit from Obj_B.someMethod, as the output section of the example demonstrates.

Obviously, use of Pre- and Post-Loggers can go a lot further than this, especially when this feature is combined with the use of sophisticated streams.


 // -- Example 17 -------------------------------------------------------------

 var Obj_A     =
    {
    method_A   : function () { },
    method_B   : function () { }
    };

 var Obj_B     =
    {
    someMethod : function ()
       {
       Obj_A.method_A ();
       Obj_A.method_B ();
       }

    };

 // ---------------------------------

 var Logger = createLogger ();          // Assume createLogger has been instantiated
                                        // as shown in Examples 2 or 4.
 Logger.applyLogDef (Obj_A, { });
 Logger.applyLogDef (Obj_B,
    {
    preLogger  : function (Args, MethodOwner, MethodName)
       {
       console.group    ("Obj_B." + MethodName + " Entry");
       },

    postLogger : function (Args, MethodOwner, MethodName)
       {
       console.groupEnd ();
       console.log      ("Obj_B." + MethodName + " Exit");
       }

    });

 Obj_B .someMethod ();

 -- Output --------------------------------------------------------------------

 Obj_B.someMethod Entry
    method_A Entry
    method_A Exit
    method_B Entry
    method_B Exit
 Obj_B.someMethod Exit
            

Pre-/Post-Loggers and Suspension/Resumption

Note that, it is possible for Pre- and Post-Loggers to call suspend and resume methods of a logger object as they execute. This means that, should a preLogger call suspend this will prevent subsequent execution of any postLogger that is carried by the LogDef in question. It follows that if a postLogger possesses a Logger.resume call, but the logger has been suspended, that postLogger will never execute and so will be unable to resume logging operations.

This issue applies also to the use of suspension/resumption with Pre- and Post-Validators in AJS_Validator.

Factory Methods

As with postValidators in the context of AJS_Validator, postLogger functions are indispensable when used in conjunction with methods that act as object factories, as they allow us not only to log the dynamic creation of objects in a system, but to apply LogDefs to the methods of such objects.

Example 18 demonstrates this concept. Here, ObjCreator possesses one method only – createObject – that creates objects comprised of three methods. The code creates a logger that uses the default stream, and defines a LogDef for ObjCreator, within which a postLogger is defined.

The code then applies that LogDef, and then calls createObject. Duly, this returns an object, at which point the postLogger executes, and applies a LogDef (defined internally within the postLogger) to the newly-created object that createObject returned. Calls to the methods of the newly-created object then verify the emplacement of logging for those methods.

Note that, although the entry to createObject appears in the log, there is no corresponding report of its return. This is because the presence of the postLogger in the LogDef overrides the logger's default behaviour, which is to call the onMethodExit method of the stream. To see a 'createObject Exit' appear in the log, we would have to include an appropriate explicit-call to the stream within the postLogger (which is passed 'Stream' and 'LogDef' as its fourth and fifth arguments, as the section above states, although these are not shown in the example).


 // -- Example 18 -------------------------------------------------------------

 var Logger      = createLogger ();

 var ObjCreator  =
    {
    createObject : function ()
       {
       return {

          method_A : function () { },
          method_B : function () { },
          method_C : function () { }

          };

       },

    };

 //----------------------------------

 Logger.applyLogDef (ObjCreator,
    {
    postLogger : function (Args, MethodOwner, MethodName, Stream, LogDef, RtnVal)
       {
       Logger.applyLogDef (RtnVal,
          {
          MethodNames :
             [
             "method_A",
             "method_C"
             ]

          });

       }

    });

 var Obj = ObjCreator.createObject ();

 Obj.method_A ();                      // Is logged.
 Obj.method_B ();                      // Is not logged.
 Obj.method_C ();                      // Is logged.

 -- Output --------------------------------------------------------------------

 createObject Entry
 method_A Entry
 method_A Exit
 method_C Entry
 method_C Exit