Friday, February 6, 2015

Logging Continued

In my last blog post you saw how you can define a simple logging facility.  Now I need to show you how to implement it :-)  Let's start by implementing the prototypical instance I mentioned in the previous blog post.  Let's define the static configuration method as the following.  If the prototype has not yet been created, we'll do that here first.

+ (MDBLog *)configuration {

    static MDBLog *prototypeLog = nil;
    @synchronized(self) {
        
        //Initial defaults, can be overridden.
        if (prototypeLog == nil) {
            
            prototypeLog = [[self alloc] init];
        
            //Set the defaults
            prototypeLog.logLevelMDBLogLevelError;
            prototypeLog.logContext = [NSString stringWithFormat:@"%@", [MDBLog class]];
            prototypeLog.logFormat = @"[%@] (%@:%@): %@";
            prototypeLog.logWriter = [MDBLog defaultLogWriter];
        }
    }
    
    return prototypeLog;
}

First, we create the static Log prototype.  Then we set the defaults for that prototype.  Lastly, we return that static instance that can be configured.

You'll notice that I set the log level to Error (the highest, least detailed), use the default log writer which writes to the console, and give a message string format that will show something like the following.  This of course can be configured.

"[LOGLEVEL] (CLASS:METHOD): LOG MESSAGE PARAMETERIZED WITH VALUES"

When we create a new Log instance, we'll use one of the following initialization methods which uses the prototypical instance to set the defaults:

- (id)initWithClass:(Class)logClass {
    
    return [self initWithContext:[NSString stringWithFormat:@"%@", logClass]];
}

- (id)initWithContext:(NSString *)logContext {
    
    if (self = [super init]) {
        
        _logContext = logContext;
        _logFormat = [MDBLog configuration].logFormat;
        _logLevel = [MDBLog configuration].logLevel;
        _logWriter = [MDBLog configuration].logWriter;
    }
    
    return self;
}

The logging methods are just as you'd expect them to be.  Here they are for "Error" log level, the others are similar:

- (BOOL)errorEnabled {
    
    return [self levelEnabled:MDBLogLevelError];
}

- (void)error:(SEL)method format:(NSString *)format, ... {
    
    va_list args;
    va_start(args, format);
    
    [self logAt:MDBLogLevelError method:method format:format args:args];
}

The generic methods for logging are defined as the following:

- (BOOL)levelEnabled:(enum MDBLogLevel)intentLevel {

    return self.logLevel <= intentLevel;
}

- (void)logAt:(int)intentLevel method:(SEL)method format:(NSString *)format args:(va_list)args {
    
    [self.logWriter log:self logLevel:intentLevel method:method format:format args:args];
}

In this case, all the information is sent to the configured Log Writer, including the variable parameter list and associated log message format string.  In order to write out to the console, we will implement the Log Writer like so:

@implementation MDBLogWriterDefault : NSObject

- (void)log:(MDBLog *)log logLevel:(enum MDBLogLevel)logLevel method:(SEL)method format:(NSString *)format args:(va_list)args {
    
    if ([log levelEnabled:logLevel]) {

        NSString *str = [[NSString alloc] initWithFormat:format arguments:args];
        NSLog(log.logFormat, [MDBLog nameOfLevel:logLevel], log.logContext, NSStringFromSelector(method), str);
    }
}

Of course, you can provide your own Log Writer that could pipe the log message to any other output or another logging facility.

An example use of this Logging facility is something like the following:

// Unnecessary, but done to show how can set custom writer.
[[MDBLog configuration] setLogWriter:[MDBLog defaultLogWriter]];

#ifdef DEBUG
[[MDBLog configuration] setLogLevel:MDBLogLevelDebug];
#else
[[MDBLog configuration] setLogLevel:MDBLogLevelWarn];
#endif

_log = [MDBLog logWithClass:[self class]];

...

[self.log debug:_cmd format:@"Formatted log message with key/value pair: %@=%@", key, value];

And that's it!  Simple and powerful.  Read more to get the full implementation here...

MDBLog.m

#import "MDBLog.h"

@implementation MDBLogWriterDefault : NSObject

- (void)log:(MDBLog *)log logLevel:(enum MDBLogLevel)logLevel method:(SEL)method format:(NSString *)format args:(va_list)args {
    
    if ([log levelEnabled:logLevel]) {

        NSString *str = [[NSString alloc] initWithFormat:format arguments:args];
        NSLog(log.logFormat, [MDBLog nameOfLevel:logLevel], log.logContext, NSStringFromSelector(method), str);
    }
}

@end

@interface MDBLog ()

@property (readwrite, nonatomic, copy) NSString *logContext;
@property (readwrite, nonatomic, copy) NSString *logFormat;

- (id)initWithClass:(Class)logClass;
- (id)initWithContext:(NSString *)logContext;
- (void)logAt:(int)intentLevel method:(SEL)method format:(NSString *)format args:(va_list)args;

@end

@implementation MDBLog

+ (MDBLog *)configuration {

    static MDBLog *prototypeLog = nil;
    @synchronized(self) {
        
        //Initial defaults, can be overridden.
        if (prototypeLog == nil) {
            
            prototypeLog = [[self alloc] init];
        
            //Set the defaults
            prototypeLog.logLevelMDBLogLevelError;
            prototypeLog.logContext = [NSString stringWithFormat:@"%@", [MDBLog class]];
            prototypeLog.logFormat = @"[%@] (%@:%@): %@";
            prototypeLog.logWriter = [MDBLog defaultLogWriter];
        }
    }
    
    return prototypeLog;
}

+ (MDBLog *)logWithClass:(Class)logClass {
    
    return [MDBLog logWithContext:[NSString stringWithFormat:@"%@", logClass]];
}

+ (MDBLog *)logWithContext:(NSString *)logContext {

    MDBLog *proto = [MDBLog configuration];
    
    return proto == nil ? nil : [[MDBLog alloc] initWithContext:logContext];
}

+ (NSString *)nameOfLevel:(int)logLevel {
    
    NSString *result = @"UNKNOWN";
    
    switch (logLevel) {
        case MDBLogLevelFine:
            result = @"FINE";
            break;
        case MDBLogLevelDebug:
            result = @"DEBUG";
            break;
        case MDBLogLevelInfo:
            result = @"INFO";
            break;
        case MDBLogLevelWarn:
            result = @"WARN";
            break;
        case MDBLogLevelError:
            result = @"ERROR";
            break;
    }
    
    return result;
}

+ (id<MDBLogWriter>)defaultLogWriter {
    
    static id<MDBLogWriter> defaultLogWriter = nil;
    @synchronized(self) {
        if (defaultLogWriter == nil) {
            defaultLogWriter = [[MDBLogWriterDefault alloc] init];
        }
    }

    return defaultLogWriter;
}

- (id)initWithClass:(Class)logClass {
    
    return [self initWithContext:[NSString stringWithFormat:@"%@", logClass]];
}

- (id)initWithContext:(NSString *)logContext {
    
    if (self = [super init]) {
        
        _logContext = logContext;
        _logFormat = [MDBLog configuration].logFormat;
        _logLevel = [MDBLog configuration].logLevel;
        _logWriter = [MDBLog configuration].logWriter;
    }
    
    return self;
}

- (BOOL)levelEnabled:(enum MDBLogLevel)intentLevel {

    return self.logLevel <= intentLevel;
}

- (BOOL)fineEnabled {
    
    return [self levelEnabled:MDBLogLevelFine];
}

- (BOOL)debugEnabled {
    
    return [self levelEnabled:MDBLogLevelDebug];
}

- (BOOL)infoEnabled {
    
    return [self levelEnabled:MDBLogLevelInfo];
}

- (BOOL)warnEnabled {
    
    return [self levelEnabled:MDBLogLevelWarn];
}

- (BOOL)errorEnabled {
    
    return [self levelEnabled:MDBLogLevelError];
}

- (void)logAt:(int)intentLevel method:(SEL)method format:(NSString *)format args:(va_list)args {
    
    [self.logWriter log:self logLevel:intentLevel method:method format:format args:args];
}

- (void)log:(enum MDBLogLevel)logLevel method:(SEL)method format:(NSString *)format, ... {
    
    va_list args;
    va_start(args, format);
    
    [self logAt:logLevel method:method format:format args:args];
}

- (void)fine:(SEL)method format:(NSString *)format, ... {
    
    va_list args;
    va_start(args, format);
    
    [self logAt:MDBLogLevelFine method:method format:format args:args];
}

- (void)debug:(SEL)method format:(NSString *)format, ... {
    
    va_list args;
    va_start(args, format);
    
    [self logAt:MDBLogLevelDebug method:method format:format args:args];
}

- (void)info:(SEL)method format:(NSString *)format, ... {
    
    va_list args;
    va_start(args, format);
    
    [self logAt:MDBLogLevelInfo method:method format:format args:args];
}

- (void)warn:(SEL)method format:(NSString *)format, ... {
    
    va_list args;
    va_start(args, format);
    
    [self logAt:MDBLogLevelWarn method:method format:format args:args];
}

- (void)error:(SEL)method format:(NSString *)format, ... {
    
    va_list args;
    va_start(args, format);
    
    [self logAt:MDBLogLevelError method:method format:format args:args];
}

@end

No comments:

Post a Comment