Basic Logging

To be able to implement Logging as a separate layer, we need to make some changes to the Functional Layer's code. The specific requirements are:

  1. Virtual stub functions for logging need to be added to the Functional Class.
  2. Any functions that need to be wrapped need to be virtual.
  3. Any functions that need to be wrapped cannot be static or private.
  4. Error handling is contained inside of the Functional Layer.
  5. Factories must be used to generate any new instances of a class.

The purpose of the stub functions is simple: anytime the class needs to send something to be logged, it calls the stub function. The Logging Layer will override the stub function, and then redirect the data passed in to the appropriate location.

Any error handling is part of the business logic, and as such, must be kept in the Functional Layer. If we were to place a catch block in the Logging Layer, then we would have one of two situations: the Functional code has a dependency upon the Logging Layer, or the business logic will be changed due to the introduction of an unexpected catch. Either of these situations will cause unnecessary complications in the maintenance of the code.

Below, we have an example of a class that has the minimal set up for having a Logging class decorate it: namespace FunctionalLayer.Services { public class ServiceOne { ... public ServiceOne(...) { ... } public Response CallA(Request request) { ... Log(LogLevel.Info, "Something useful"); ... if (errorCondition) { Log(LogLevel.Error, "Something bad happened"); } ... } protected virtual void Log(LogLevel level, string logMessage) {} } }

Note: in theory we might not need the Log() stub. However, by using it here, we are able to add specific transient data to the logging that would not otherwise be available at the Logging Layer. For example, a we could log an error where a specific value is out of range, as well as what the valid range should be. See Additional Logging Considerations for more details.

Now, we look at a Logging class that will decorate the above class. At its most simple form, it needs to to do two things: 1) make a connection to logging system, and 2) override the Log() stub so any logging sent to it is actually sent to the logging system. Note that if the Functional Layer class has multiple constructors, then the Logging Layer class most likely will need multiple constructors as well. namespace Logging.Services { public class ServiceOne : FunctionalLayer.Services.ServiceOne { // writes to a file, or a service // initialized during construction private ILogger _logger; private LoggingContext _context; ... public ServiceOne(..., ILogger logger) : base(...) { _logger = logger; } protected override void Log(LogLevel level, string logMessage) { _logger.WriteLine(level, logMessage); } } }

Note: the LoggingContext object will be covered later in Other Logging Considerations.

Next: Start / Stop Logging