michael@0: // michael@0: // GTMLogger.h michael@0: // michael@0: // Copyright 2007-2008 Google Inc. michael@0: // michael@0: // Licensed under the Apache License, Version 2.0 (the "License"); you may not michael@0: // use this file except in compliance with the License. You may obtain a copy michael@0: // of the License at michael@0: // michael@0: // http://www.apache.org/licenses/LICENSE-2.0 michael@0: // michael@0: // Unless required by applicable law or agreed to in writing, software michael@0: // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT michael@0: // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the michael@0: // License for the specific language governing permissions and limitations under michael@0: // the License. michael@0: // michael@0: michael@0: // Key Abstractions michael@0: // ---------------- michael@0: // michael@0: // This file declares multiple classes and protocols that are used by the michael@0: // GTMLogger logging system. The 4 main abstractions used in this file are the michael@0: // following: michael@0: // michael@0: // * logger (GTMLogger) - The main logging class that users interact with. It michael@0: // has methods for logging at different levels and uses a log writer, a log michael@0: // formatter, and a log filter to get the job done. michael@0: // michael@0: // * log writer (GTMLogWriter) - Writes a given string to some log file, where michael@0: // a "log file" can be a physical file on disk, a POST over HTTP to some URL, michael@0: // or even some in-memory structure (e.g., a ring buffer). michael@0: // michael@0: // * log formatter (GTMLogFormatter) - Given a format string and arguments as michael@0: // a va_list, returns a single formatted NSString. A "formatted string" could michael@0: // be a string with the date prepended, a string with values in a CSV format, michael@0: // or even a string of XML. michael@0: // michael@0: // * log filter (GTMLogFilter) - Given a formatted log message as an NSString michael@0: // and the level at which the message is to be logged, this class will decide michael@0: // whether the given message should be logged or not. This is a flexible way michael@0: // to filter out messages logged at a certain level, messages that contain michael@0: // certain text, or filter nothing out at all. This gives the caller the michael@0: // flexibility to dynamically enable debug logging in Release builds. michael@0: // michael@0: // This file also declares some classes to handle the common log writer, log michael@0: // formatter, and log filter cases. Callers can also create their own writers, michael@0: // formatters, and filters and they can even build them on top of the ones michael@0: // declared here. Keep in mind that your custom writer/formatter/filter may be michael@0: // called from multiple threads, so it must be thread-safe. michael@0: michael@0: #import michael@0: #import "GTMDefines.h" michael@0: michael@0: // Predeclaration of used protocols that are declared later in this file. michael@0: @protocol GTMLogWriter, GTMLogFormatter, GTMLogFilter; michael@0: michael@0: // GTMLogger michael@0: // michael@0: // GTMLogger is the primary user-facing class for an object-oriented logging michael@0: // system. It is built on the concept of log formatters (GTMLogFormatter), log michael@0: // writers (GTMLogWriter), and log filters (GTMLogFilter). When a message is michael@0: // sent to a GTMLogger to log a message, the message is formatted using the log michael@0: // formatter, then the log filter is consulted to see if the message should be michael@0: // logged, and if so, the message is sent to the log writer to be written out. michael@0: // michael@0: // GTMLogger is intended to be a flexible and thread-safe logging solution. Its michael@0: // flexibility comes from the fact that GTMLogger instances can be customized michael@0: // with user defined formatters, filters, and writers. And these writers, michael@0: // filters, and formatters can be combined, stacked, and customized in arbitrary michael@0: // ways to suit the needs at hand. For example, multiple writers can be used at michael@0: // the same time, and a GTMLogger instance can even be used as another michael@0: // GTMLogger's writer. This allows for arbitrarily deep logging trees. michael@0: // michael@0: // A standard GTMLogger uses a writer that sends messages to standard out, a michael@0: // formatter that smacks a timestamp and a few other bits of interesting michael@0: // information on the message, and a filter that filters out debug messages from michael@0: // release builds. Using the standard log settings, a log message will look like michael@0: // the following: michael@0: // michael@0: // 2007-12-30 10:29:24.177 myapp[4588/0xa07d0f60] [lvl=1] foo= michael@0: // michael@0: // The output contains the date and time of the log message, the name of the michael@0: // process followed by its process ID/thread ID, the log level at which the michael@0: // message was logged (in the previous example the level was 1: michael@0: // kGTMLoggerLevelDebug), and finally, the user-specified log message itself (in michael@0: // this case, the log message was @"foo=%@", foo). michael@0: // michael@0: // Multiple instances of GTMLogger can be created, each configured their own michael@0: // way. Though GTMLogger is not a singleton (in the GoF sense), it does provide michael@0: // access to a shared (i.e., globally accessible) GTMLogger instance. This makes michael@0: // it convenient for all code in a process to use the same GTMLogger instance. michael@0: // The shared GTMLogger instance can also be configured in an arbitrary, and michael@0: // these configuration changes will affect all code that logs through the shared michael@0: // instance. michael@0: michael@0: // michael@0: // Log Levels michael@0: // ---------- michael@0: // GTMLogger has 3 different log levels: Debug, Info, and Error. GTMLogger michael@0: // doesn't take any special action based on the log level; it simply forwards michael@0: // this information on to formatters, filters, and writers, each of which may michael@0: // optionally take action based on the level. Since log level filtering is michael@0: // performed at runtime, log messages are typically not filtered out at compile michael@0: // time. The exception to this rule is that calls to the GTMLoggerDebug() macro michael@0: // *ARE* filtered out of non-DEBUG builds. This is to be backwards compatible michael@0: // with behavior that many developers are currently used to. Note that this michael@0: // means that GTMLoggerDebug(@"hi") will be compiled out of Release builds, but michael@0: // [[GTMLogger sharedLogger] logDebug:@"hi"] will NOT be compiled out. michael@0: // michael@0: // Standard loggers are created with the GTMLogLevelFilter log filter, which michael@0: // filters out certain log messages based on log level, and some other settings. michael@0: // michael@0: // In addition to the -logDebug:, -logInfo:, and -logError: methods defined on michael@0: // GTMLogger itself, there are also C macros that make usage of the shared michael@0: // GTMLogger instance very convenient. These macros are: michael@0: // michael@0: // GTMLoggerDebug(...) michael@0: // GTMLoggerInfo(...) michael@0: // GTMLoggerError(...) michael@0: // michael@0: // Again, a notable feature of these macros is that GTMLogDebug() calls *will be michael@0: // compiled out of non-DEBUG builds*. michael@0: // michael@0: // Standard Loggers michael@0: // ---------------- michael@0: // GTMLogger has the concept of "standard loggers". A standard logger is simply michael@0: // a logger that is pre-configured with some standard/common writer, formatter, michael@0: // and filter combination. Standard loggers are created using the creation michael@0: // methods beginning with "standard". The alternative to a standard logger is a michael@0: // regular logger, which will send messages to stdout, with no special michael@0: // formatting, and no filtering. michael@0: // michael@0: // How do I use GTMLogger? michael@0: // ---------------------- michael@0: // The typical way you will want to use GTMLogger is to simply use the michael@0: // GTMLogger*() macros for logging from code. That way we can easily make michael@0: // changes to the GTMLogger class and simply update the macros accordingly. Only michael@0: // your application startup code (perhaps, somewhere in main()) should use the michael@0: // GTMLogger class directly in order to configure the shared logger, which all michael@0: // of the code using the macros will be using. Again, this is just the typical michael@0: // situation. michael@0: // michael@0: // To be complete, there are cases where you may want to use GTMLogger directly, michael@0: // or even create separate GTMLogger instances for some reason. That's fine, michael@0: // too. michael@0: // michael@0: // Examples michael@0: // -------- michael@0: // The following show some common GTMLogger use cases. michael@0: // michael@0: // 1. You want to log something as simply as possible. Also, this call will only michael@0: // appear in debug builds. In non-DEBUG builds it will be completely removed. michael@0: // michael@0: // GTMLoggerDebug(@"foo = %@", foo); michael@0: // michael@0: // 2. The previous example is similar to the following. The major difference is michael@0: // that the previous call (example 1) will be compiled out of Release builds michael@0: // but this statement will not be compiled out. michael@0: // michael@0: // [[GTMLogger sharedLogger] logDebug:@"foo = %@", foo]; michael@0: // michael@0: // 3. Send all logging output from the shared logger to a file. We do this by michael@0: // creating an NSFileHandle for writing associated with a file, and setting michael@0: // that file handle as the logger's writer. michael@0: // michael@0: // NSFileHandle *f = [NSFileHandle fileHandleForWritingAtPath:@"/tmp/f.log" michael@0: // create:YES]; michael@0: // [[GTMLogger sharedLogger] setWriter:f]; michael@0: // GTMLoggerError(@"hi"); // This will be sent to /tmp/f.log michael@0: // michael@0: // 4. Create a new GTMLogger that will log to a file. This example differs from michael@0: // the previous one because here we create a new GTMLogger that is different michael@0: // from the shared logger. michael@0: // michael@0: // GTMLogger *logger = [GTMLogger standardLoggerWithPath:@"/tmp/temp.log"]; michael@0: // [logger logInfo:@"hi temp log file"]; michael@0: // michael@0: // 5. Create a logger that writes to stdout and does NOT do any formatting to michael@0: // the log message. This might be useful, for example, when writing a help michael@0: // screen for a command-line tool to standard output. michael@0: // michael@0: // GTMLogger *logger = [GTMLogger logger]; michael@0: // [logger logInfo:@"%@ version 0.1 usage", progName]; michael@0: // michael@0: // 6. Send log output to stdout AND to a log file. The trick here is that michael@0: // NSArrays function as composite log writers, which means when an array is michael@0: // set as the log writer, it forwards all logging messages to all of its michael@0: // contained GTMLogWriters. michael@0: // michael@0: // // Create array of GTMLogWriters michael@0: // NSArray *writers = [NSArray arrayWithObjects: michael@0: // [NSFileHandle fileHandleForWritingAtPath:@"/tmp/f.log" create:YES], michael@0: // [NSFileHandle fileHandleWithStandardOutput], nil]; michael@0: // michael@0: // GTMLogger *logger = [GTMLogger standardLogger]; michael@0: // [logger setWriter:writers]; michael@0: // [logger logInfo:@"hi"]; // Output goes to stdout and /tmp/f.log michael@0: // michael@0: // For futher details on log writers, formatters, and filters, see the michael@0: // documentation below. michael@0: // michael@0: // NOTE: GTMLogger is application level logging. By default it does nothing michael@0: // with _GTMDevLog/_GTMDevAssert (see GTMDefines.h). An application can choose michael@0: // to bridge _GTMDevLog/_GTMDevAssert to GTMLogger by providing macro michael@0: // definitions in its prefix header (see GTMDefines.h for how one would do michael@0: // that). michael@0: // michael@0: @interface GTMLogger : NSObject { michael@0: @private michael@0: id writer_; michael@0: id formatter_; michael@0: id filter_; michael@0: } michael@0: michael@0: // michael@0: // Accessors for the shared logger instance michael@0: // michael@0: michael@0: // Returns a shared/global standard GTMLogger instance. Callers should typically michael@0: // use this method to get a GTMLogger instance, unless they explicitly want michael@0: // their own instance to configure for their own needs. This is the only method michael@0: // that returns a shared instance; all the rest return new GTMLogger instances. michael@0: + (id)sharedLogger; michael@0: michael@0: // Sets the shared logger instance to |logger|. Future calls to +sharedLogger michael@0: // will return |logger| instead. michael@0: + (void)setSharedLogger:(GTMLogger *)logger; michael@0: michael@0: // michael@0: // Creation methods michael@0: // michael@0: michael@0: // Returns a new autoreleased GTMLogger instance that will log to stdout, using michael@0: // the GTMLogStandardFormatter, and the GTMLogLevelFilter filter. michael@0: + (id)standardLogger; michael@0: michael@0: // Same as +standardLogger, but logs to stderr. michael@0: + (id)standardLoggerWithStderr; michael@0: michael@0: // Same as +standardLogger but levels >= kGTMLoggerLevelError are routed to michael@0: // stderr, everything else goes to stdout. michael@0: + (id)standardLoggerWithStdoutAndStderr; michael@0: michael@0: // Returns a new standard GTMLogger instance with a log writer that will michael@0: // write to the file at |path|, and will use the GTMLogStandardFormatter and michael@0: // GTMLogLevelFilter classes. If |path| does not exist, it will be created. michael@0: + (id)standardLoggerWithPath:(NSString *)path; michael@0: michael@0: // Returns an autoreleased GTMLogger instance that will use the specified michael@0: // |writer|, |formatter|, and |filter|. michael@0: + (id)loggerWithWriter:(id)writer michael@0: formatter:(id)formatter michael@0: filter:(id)filter; michael@0: michael@0: // Returns an autoreleased GTMLogger instance that logs to stdout, with the michael@0: // basic formatter, and no filter. The returned logger differs from the logger michael@0: // returned by +standardLogger because this one does not do any filtering and michael@0: // does not do any special log formatting; this is the difference between a michael@0: // "regular" logger and a "standard" logger. michael@0: + (id)logger; michael@0: michael@0: // Designated initializer. This method returns a GTMLogger initialized with the michael@0: // specified |writer|, |formatter|, and |filter|. See the setter methods below michael@0: // for what values will be used if nil is passed for a parameter. michael@0: - (id)initWithWriter:(id)writer michael@0: formatter:(id)formatter michael@0: filter:(id)filter; michael@0: michael@0: // michael@0: // Logging methods michael@0: // michael@0: michael@0: // Logs a message at the debug level (kGTMLoggerLevelDebug). michael@0: - (void)logDebug:(NSString *)fmt, ... NS_FORMAT_FUNCTION(1, 2); michael@0: // Logs a message at the info level (kGTMLoggerLevelInfo). michael@0: - (void)logInfo:(NSString *)fmt, ... NS_FORMAT_FUNCTION(1, 2); michael@0: // Logs a message at the error level (kGTMLoggerLevelError). michael@0: - (void)logError:(NSString *)fmt, ... NS_FORMAT_FUNCTION(1, 2); michael@0: // Logs a message at the assert level (kGTMLoggerLevelAssert). michael@0: - (void)logAssert:(NSString *)fmt, ... NS_FORMAT_FUNCTION(1, 2); michael@0: michael@0: michael@0: // michael@0: // Accessors michael@0: // michael@0: michael@0: // Accessor methods for the log writer. If the log writer is set to nil, michael@0: // [NSFileHandle fileHandleWithStandardOutput] is used. michael@0: - (id)writer; michael@0: - (void)setWriter:(id)writer; michael@0: michael@0: // Accessor methods for the log formatter. If the log formatter is set to nil, michael@0: // GTMLogBasicFormatter is used. This formatter will format log messages in a michael@0: // plain printf style. michael@0: - (id)formatter; michael@0: - (void)setFormatter:(id)formatter; michael@0: michael@0: // Accessor methods for the log filter. If the log filter is set to nil, michael@0: // GTMLogNoFilter is used, which allows all log messages through. michael@0: - (id)filter; michael@0: - (void)setFilter:(id)filter; michael@0: michael@0: @end // GTMLogger michael@0: michael@0: michael@0: // Helper functions that are used by the convenience GTMLogger*() macros that michael@0: // enable the logging of function names. michael@0: @interface GTMLogger (GTMLoggerMacroHelpers) michael@0: - (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ... michael@0: NS_FORMAT_FUNCTION(2, 3); michael@0: - (void)logFuncInfo:(const char *)func msg:(NSString *)fmt, ... michael@0: NS_FORMAT_FUNCTION(2, 3); michael@0: - (void)logFuncError:(const char *)func msg:(NSString *)fmt, ... michael@0: NS_FORMAT_FUNCTION(2, 3); michael@0: - (void)logFuncAssert:(const char *)func msg:(NSString *)fmt, ... michael@0: NS_FORMAT_FUNCTION(2, 3); michael@0: @end // GTMLoggerMacroHelpers michael@0: michael@0: michael@0: // The convenience macros are only defined if they haven't already been defined. michael@0: #ifndef GTMLoggerInfo michael@0: michael@0: // Convenience macros that log to the shared GTMLogger instance. These macros michael@0: // are how users should typically log to GTMLogger. Notice that GTMLoggerDebug() michael@0: // calls will be compiled out of non-Debug builds. michael@0: #define GTMLoggerDebug(...) \ michael@0: [[GTMLogger sharedLogger] logFuncDebug:__func__ msg:__VA_ARGS__] michael@0: #define GTMLoggerInfo(...) \ michael@0: [[GTMLogger sharedLogger] logFuncInfo:__func__ msg:__VA_ARGS__] michael@0: #define GTMLoggerError(...) \ michael@0: [[GTMLogger sharedLogger] logFuncError:__func__ msg:__VA_ARGS__] michael@0: #define GTMLoggerAssert(...) \ michael@0: [[GTMLogger sharedLogger] logFuncAssert:__func__ msg:__VA_ARGS__] michael@0: michael@0: // If we're not in a debug build, remove the GTMLoggerDebug statements. This michael@0: // makes calls to GTMLoggerDebug "compile out" of Release builds michael@0: #ifndef DEBUG michael@0: #undef GTMLoggerDebug michael@0: #define GTMLoggerDebug(...) do {} while(0) michael@0: #endif michael@0: michael@0: #endif // !defined(GTMLoggerInfo) michael@0: michael@0: // Log levels. michael@0: typedef enum { michael@0: kGTMLoggerLevelUnknown, michael@0: kGTMLoggerLevelDebug, michael@0: kGTMLoggerLevelInfo, michael@0: kGTMLoggerLevelError, michael@0: kGTMLoggerLevelAssert, michael@0: } GTMLoggerLevel; michael@0: michael@0: michael@0: // michael@0: // Log Writers michael@0: // michael@0: michael@0: // Protocol to be implemented by a GTMLogWriter instance. michael@0: @protocol GTMLogWriter michael@0: // Writes the given log message to where the log writer is configured to write. michael@0: - (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level; michael@0: @end // GTMLogWriter michael@0: michael@0: michael@0: // Simple category on NSFileHandle that makes NSFileHandles valid log writers. michael@0: // This is convenient because something like, say, +fileHandleWithStandardError michael@0: // now becomes a valid log writer. Log messages are written to the file handle michael@0: // with a newline appended. michael@0: @interface NSFileHandle (GTMFileHandleLogWriter) michael@0: // Opens the file at |path| in append mode, and creates the file with |mode| michael@0: // if it didn't previously exist. michael@0: + (id)fileHandleForLoggingAtPath:(NSString *)path mode:(mode_t)mode; michael@0: @end // NSFileHandle michael@0: michael@0: michael@0: // This category makes NSArray a GTMLogWriter that can be composed of other michael@0: // GTMLogWriters. This is the classic Composite GoF design pattern. When the michael@0: // GTMLogWriter -logMessage:level: message is sent to the array, the array michael@0: // forwards the message to all of its elements that implement the GTMLogWriter michael@0: // protocol. michael@0: // michael@0: // This is useful in situations where you would like to send log output to michael@0: // multiple log writers at the same time. Simply create an NSArray of the log michael@0: // writers you wish to use, then set the array as the "writer" for your michael@0: // GTMLogger instance. michael@0: @interface NSArray (GTMArrayCompositeLogWriter) michael@0: @end // GTMArrayCompositeLogWriter michael@0: michael@0: michael@0: // This category adapts the GTMLogger interface so that it can be used as a log michael@0: // writer; it's an "adapter" in the GoF Adapter pattern sense. michael@0: // michael@0: // This is useful when you want to configure a logger to log to a specific michael@0: // writer with a specific formatter and/or filter. But you want to also compose michael@0: // that with a different log writer that may have its own formatter and/or michael@0: // filter. michael@0: @interface GTMLogger (GTMLoggerLogWriter) michael@0: @end // GTMLoggerLogWriter michael@0: michael@0: michael@0: // michael@0: // Log Formatters michael@0: // michael@0: michael@0: // Protocol to be implemented by a GTMLogFormatter instance. michael@0: @protocol GTMLogFormatter michael@0: // Returns a formatted string using the format specified in |fmt| and the va michael@0: // args specified in |args|. michael@0: - (NSString *)stringForFunc:(NSString *)func michael@0: withFormat:(NSString *)fmt michael@0: valist:(va_list)args michael@0: level:(GTMLoggerLevel)level NS_FORMAT_FUNCTION(2, 0); michael@0: @end // GTMLogFormatter michael@0: michael@0: michael@0: // A basic log formatter that formats a string the same way that NSLog (or michael@0: // printf) would. It does not do anything fancy, nor does it add any data of its michael@0: // own. michael@0: @interface GTMLogBasicFormatter : NSObject michael@0: michael@0: // Helper method for prettying C99 __func__ and GCC __PRETTY_FUNCTION__ michael@0: - (NSString *)prettyNameForFunc:(NSString *)func; michael@0: michael@0: @end // GTMLogBasicFormatter michael@0: michael@0: michael@0: // A log formatter that formats the log string like the basic formatter, but michael@0: // also prepends a timestamp and some basic process info to the message, as michael@0: // shown in the following sample output. michael@0: // 2007-12-30 10:29:24.177 myapp[4588/0xa07d0f60] [lvl=1] log mesage here michael@0: @interface GTMLogStandardFormatter : GTMLogBasicFormatter { michael@0: @private michael@0: NSDateFormatter *dateFormatter_; // yyyy-MM-dd HH:mm:ss.SSS michael@0: NSString *pname_; michael@0: pid_t pid_; michael@0: } michael@0: @end // GTMLogStandardFormatter michael@0: michael@0: michael@0: // michael@0: // Log Filters michael@0: // michael@0: michael@0: // Protocol to be imlemented by a GTMLogFilter instance. michael@0: @protocol GTMLogFilter michael@0: // Returns YES if |msg| at |level| should be filtered out; NO otherwise. michael@0: - (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level; michael@0: @end // GTMLogFilter michael@0: michael@0: michael@0: // A log filter that filters messages at the kGTMLoggerLevelDebug level out of michael@0: // non-debug builds. Messages at the kGTMLoggerLevelInfo level are also filtered michael@0: // out of non-debug builds unless GTMVerboseLogging is set in the environment or michael@0: // the processes's defaults. Messages at the kGTMLoggerLevelError level are michael@0: // never filtered. michael@0: @interface GTMLogLevelFilter : NSObject michael@0: @end // GTMLogLevelFilter michael@0: michael@0: // A simple log filter that does NOT filter anything out; michael@0: // -filterAllowsMessage:level will always return YES. This can be a convenient michael@0: // way to enable debug-level logging in release builds (if you so desire). michael@0: @interface GTMLogNoFilter : NSObject michael@0: @end // GTMLogNoFilter michael@0: michael@0: michael@0: // Base class for custom level filters. Not for direct use, use the minimum michael@0: // or maximum level subclasses below. michael@0: @interface GTMLogAllowedLevelFilter : NSObject { michael@0: @private michael@0: NSIndexSet *allowedLevels_; michael@0: } michael@0: @end michael@0: michael@0: // A log filter that allows you to set a minimum log level. Messages below this michael@0: // level will be filtered. michael@0: @interface GTMLogMininumLevelFilter : GTMLogAllowedLevelFilter michael@0: michael@0: // Designated initializer, logs at levels < |level| will be filtered. michael@0: - (id)initWithMinimumLevel:(GTMLoggerLevel)level; michael@0: michael@0: @end michael@0: michael@0: // A log filter that allows you to set a maximum log level. Messages whose level michael@0: // exceeds this level will be filtered. This is really only useful if you have michael@0: // a composite GTMLogger that is sending the other messages elsewhere. michael@0: @interface GTMLogMaximumLevelFilter : GTMLogAllowedLevelFilter michael@0: michael@0: // Designated initializer, logs at levels > |level| will be filtered. michael@0: - (id)initWithMaximumLevel:(GTMLoggerLevel)level; michael@0: michael@0: @end michael@0: michael@0: michael@0: // For subclasses only michael@0: @interface GTMLogger (PrivateMethods) michael@0: michael@0: - (void)logInternalFunc:(const char *)func michael@0: format:(NSString *)fmt michael@0: valist:(va_list)args michael@0: level:(GTMLoggerLevel)level NS_FORMAT_FUNCTION(2, 0); michael@0: michael@0: @end michael@0: