├── .gitignore ├── LICENSE ├── Log4Cocoa Tests ├── Log4Cocoa Tests-Info.plist ├── Log4Cocoa Tests-Prefix.pch ├── Log4Cocoa_Tests.m └── en.lproj │ └── InfoPlist.strings ├── Log4Cocoa.podspec ├── Log4Cocoa.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Log4Cocoa ├── L4AppenderAttachable.h ├── L4AppenderAttachable.m ├── L4AppenderProtocols.h ├── L4AppenderSkeleton.h ├── L4AppenderSkeleton.m ├── L4BasicConfigurator.h ├── L4BasicConfigurator.m ├── L4ConsoleAppender.h ├── L4ConsoleAppender.m ├── L4DailyRollingFileAppender.h ├── L4DailyRollingFileAppender.m ├── L4DenyAllFilter.h ├── L4DenyAllFilter.m ├── L4FileAppender.h ├── L4FileAppender.m ├── L4Filter.h ├── L4Filter.m ├── L4JSONLayout.h ├── L4JSONLayout.m ├── L4Layout.h ├── L4Layout.m ├── L4Level.h ├── L4Level.m ├── L4LevelMatchFilter.h ├── L4LevelMatchFilter.m ├── L4LevelRangeFilter.h ├── L4LevelRangeFilter.m ├── L4LogEvent.h ├── L4LogEvent.m ├── L4LogLog.h ├── L4LogLog.m ├── L4Logger.h ├── L4Logger.m ├── L4LoggerNameMatchFilter.h ├── L4LoggerNameMatchFilter.m ├── L4LoggerProtocols.h ├── L4LoggerStore.h ├── L4LoggerStore.m ├── L4Logging.h ├── L4Logging.m ├── L4PatternLayout.h ├── L4PatternLayout.m ├── L4Properties.h ├── L4Properties.m ├── L4PropertyConfigurator.h ├── L4PropertyConfigurator.m ├── L4RollingFileAppender.h ├── L4RollingFileAppender.m ├── L4RootLogger.h ├── L4RootLogger.m ├── L4SimpleLayout.h ├── L4SimpleLayout.m ├── L4StringMatchFilter.h ├── L4StringMatchFilter.m ├── L4WriterAppender.h ├── L4WriterAppender.m ├── L4XMLLayout.h ├── L4XMLLayout.m ├── Log4Cocoa-Prefix.pch ├── Log4Cocoa.h ├── Log4CocoaDefines.h ├── NSObject+Log4Cocoa.h └── NSObject+Log4Cocoa.m └── README.mdown /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # CocoaPods 23 | Pods 24 | 25 | # Other 26 | *.swp 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Endika Gutiérrez Salas 2 | Copyright (c) 2007-2009 Timothy Reaves. 3 | Portions copyright (c) 2002, 2003, Bob Frank 4 | Portions copyright (c) 2004 Michael James 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions 9 | are met: 10 | 1. Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 3. All advertising materials mentioning features or use of this software 16 | must display the following acknowledgement: 17 | This product includes software developed by the University of 18 | California, Berkeley and its contributors. 19 | 4. Neither the name of the University nor the names of its contributors 20 | may be used to endorse or promote products derived from this software 21 | without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY 24 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 27 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 28 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 30 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /Log4Cocoa Tests/Log4Cocoa Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.log4cocoa.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Log4Cocoa Tests/Log4Cocoa Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #import 10 | #endif 11 | -------------------------------------------------------------------------------- /Log4Cocoa Tests/Log4Cocoa_Tests.m: -------------------------------------------------------------------------------- 1 | // 2 | // Log4Cocoa_Tests.m 3 | // Log4Cocoa Tests 4 | // 5 | // Created by Endika Gutiérrez Salas on 12/13/13. 6 | // Copyright (c) 2013 EPD. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface Log4Cocoa_Tests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation Log4Cocoa_Tests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Log4Cocoa Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Log4Cocoa.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Log4Cocoa" 3 | s.version = "0.2.0" 4 | s.summary = "Log4j port for iOS and Mac OS X." 5 | s.homepage = "https://github.com/endSly/Log4Cocoa" 6 | s.license = 'BSD' 7 | s.author = { "StuFF mc" => "mc@stuffmc.com", "Endika Gutiérrez Salas" => "me@endika.net", "Timothy Reaves" => "", "Bob Frank" => "", "Michael James" => "" } 8 | s.source = { :git => "https://github.com/endSly/Log4Cocoa.git", :tag => s.version.to_s } 9 | s.source_files = 'Log4Cocoa', 'Log4Cocoa/**/*.{h,m}' 10 | s.requires_arc = true 11 | end 12 | -------------------------------------------------------------------------------- /Log4Cocoa.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Log4Cocoa/L4AppenderAttachable.h: -------------------------------------------------------------------------------- 1 | // For copyright & license, see LICENSE. 2 | 3 | #import 4 | #import "L4AppenderProtocols.h" 5 | 6 | extern NSString * const kL4AppenderAddedEvent; 7 | extern NSString * const kL4AppenderRemovedEvent; 8 | 9 | /** 10 | * This (poorly named) class servers as the basis for the L4AppenderAttachable protocol. 11 | */ 12 | @interface L4AppenderAttachable : NSObject 13 | /** 14 | * Appends event to appenders. 15 | * This message sends a doAppend: message to every appender in the appnderList attribute. 16 | * @param event the event to be appended. 17 | * @return the number of appenders the event was appended to. 18 | */ 19 | - (NSUInteger)appendLoopOnAppenders:(L4LogEvent *)event; 20 | 21 | @end 22 | 23 | -------------------------------------------------------------------------------- /Log4Cocoa/L4AppenderAttachable.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4AppenderAttachable.h" 6 | 7 | NSString * const kL4AppenderAddedEvent = @"kL4AppenderAddedEvent"; 8 | NSString * const kL4AppenderRemovedEvent = @"kL4AppenderRemovedEvent"; 9 | 10 | @implementation L4AppenderAttachable { 11 | NSMutableArray * _appenderList; /**< The collection of log appenders.*/ 12 | } 13 | 14 | - (NSUInteger)appendLoopOnAppenders:(L4LogEvent *)event 15 | { 16 | @synchronized(self) { 17 | for (id appender in _appenderList) 18 | [appender doAppend:event]; 19 | } 20 | return _appenderList.count; 21 | } 22 | 23 | #pragma mark Appender Attachable Methods protocol methods 24 | 25 | - (void)addAppender:(id )appender 26 | { 27 | if (!appender) // sanity check 28 | return; 29 | 30 | @synchronized(self) { 31 | if (!_appenderList) { 32 | // only place appenderList array is recreated if its nil. 33 | _appenderList = [NSMutableArray arrayWithObject:appender]; 34 | return; 35 | } 36 | 37 | if(![_appenderList containsObject:appender]) { 38 | [_appenderList addObject:appender]; 39 | [[NSNotificationCenter defaultCenter] postNotificationName:kL4AppenderAddedEvent object:appender]; 40 | } 41 | } 42 | } 43 | 44 | - (NSArray *)allAppenders 45 | { 46 | return _appenderList; 47 | } 48 | 49 | - (id )appenderWithName:(NSString *)name 50 | { 51 | return [_appenderList filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"name = %@", name]].lastObject; 52 | } 53 | 54 | - (BOOL)isAttached:(id )appender 55 | { 56 | if (!appender || !_appenderList) 57 | return NO; // short circuit the test 58 | 59 | return [_appenderList containsObject:appender]; 60 | } 61 | 62 | - (void) removeAppenderWithName:(NSString *)name 63 | { 64 | [self removeAppender:[self appenderWithName:name]]; 65 | } 66 | 67 | - (void) removeAppender:(id ) appender 68 | { 69 | [_appenderList removeObject:appender]; 70 | [[NSNotificationCenter defaultCenter] postNotificationName:kL4AppenderRemovedEvent object:appender]; 71 | } 72 | 73 | - (void) removeAllAppenders 74 | { 75 | @synchronized(self) { 76 | for (id appender in _appenderList) { 77 | [[NSNotificationCenter defaultCenter] postNotificationName:kL4AppenderRemovedEvent object:appender]; 78 | } 79 | [_appenderList removeAllObjects]; 80 | _appenderList = nil; 81 | } 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /Log4Cocoa/L4AppenderProtocols.h: -------------------------------------------------------------------------------- 1 | // For copyright & license, see LICENSE. 2 | 3 | #import 4 | 5 | @class L4Logger, L4Filter, L4Layout, L4LogEvent, L4Properties; 6 | 7 | /** 8 | * Appenders are responsible for adding a log message to log. 9 | * This formal protocol defines the messages a class used for appending needs to support. 10 | */ 11 | @protocol L4Appender 12 | 13 | /** 14 | * Accessor for unique name attribute for this appender. 15 | */ 16 | @property (readwrite) NSString * name; 17 | 18 | /** 19 | * Accessor for layout of this appender. 20 | */ 21 | @property (readwrite) L4Layout * layout; 22 | 23 | /** 24 | * Returns if the appender requires layout. 25 | */ 26 | @property (readonly) BOOL requiresLayout; 27 | 28 | /** 29 | * Initializes an instance from a collection of configuration properties. 30 | * For more information on the specific appender properties, see the documentation for the particular class. 31 | * @param initProperties the properties to use. 32 | */ 33 | - (id)initWithProperties:(L4Properties *)initProperties; 34 | 35 | /** 36 | * Appender log this event. 37 | * @param anEvent the event to append. 38 | */ 39 | - (void)doAppend:(L4LogEvent *)anEvent; 40 | 41 | /** 42 | * Appends to the end of list. 43 | * @param newFilter the filter to add. 44 | */ 45 | - (void)appendFilter:(L4Filter *)newFilter; 46 | 47 | /** 48 | * Accessor for the head filter (the first in the list). 49 | * @return first filter or nil if there are none. 50 | */ 51 | - (L4Filter *)headFilter; 52 | 53 | /** 54 | * Removes all filters from list. 55 | */ 56 | - (void)clearFilters; 57 | 58 | /** 59 | * It is a programing error to append to a close appender. 60 | */ 61 | - (void)close; 62 | 63 | @end 64 | 65 | 66 | /** 67 | * This protocol defines messages used to chain L4Appender instances together. The system supports having more than one 68 | * logging appender, so that a single logging event can be logged in more than one place. 69 | */ 70 | @protocol L4AppenderAttachable 71 | /** 72 | * Adds an appender to be logged to. 73 | * @param newAppender the new appender to add. 74 | */ 75 | - (void)addAppender:(id )newAppender; 76 | 77 | /** 78 | * Accessor for the collection of log appenders. 79 | * @return an array of al appenders. 80 | */ 81 | - (NSArray *)allAppenders; 82 | 83 | /** 84 | * Returns the L4Appender with the given name. 85 | * @param aName the name of the L4Appender of interest. 86 | * @return the L4Appender with the name aName, or nil if it does not exist. 87 | */ 88 | - (id )appenderWithName:(NSString *)aName; 89 | 90 | /** 91 | * Returns a BOOL value that indicates whether the parameter has been attached to the appender list. 92 | * @param appender the L4Appender of interest. 93 | * @return YES if appender has been attached, NO otherwise. 94 | */ 95 | - (BOOL)isAttached:(id )appender; 96 | 97 | /** 98 | * Clears all L4Appender instances that have been attached. 99 | */ 100 | - (void)removeAllAppenders; 101 | 102 | /** 103 | * Removes a given L4Appender from those attached. 104 | * @param appender the L4Appender to remove. 105 | */ 106 | - (void)removeAppender:(id )appender; 107 | 108 | /** 109 | * Removes a L4Appender with the given name from those attached. 110 | * @param aName the name of the L4Appender to remove. 111 | */ 112 | - (void)removeAppenderWithName:(NSString *)name; 113 | 114 | @end 115 | 116 | -------------------------------------------------------------------------------- /Log4Cocoa/L4AppenderSkeleton.h: -------------------------------------------------------------------------------- 1 | // For copyright & license, see LICENSE. 2 | 3 | #import 4 | #import "L4AppenderProtocols.h" 5 | 6 | @class L4Filter, L4Level, L4LogEvent, L4Properties; 7 | 8 | /** 9 | * This class acts as a superclass for classes that want to log. It is not intended 10 | * to be instantiated, but as Objective C does not have the concept of abstract classes, 11 | * and as protocols can't have implementations, this class simply impliments some 12 | * standard, generic logging behaviour. 13 | */ 14 | @interface L4AppenderSkeleton : NSObject { 15 | L4Filter * _headFilter; /**< The first filter used by this appender.*/ 16 | L4Filter __weak * _tailFilter; /**< The last filter used by this appender.*/ 17 | BOOL _closed; /**< Tracks if this appender has been closed.*/ 18 | } 19 | 20 | /** Accessor for the threshold attribute that tracks the level at wich this appnded will log an event.*/ 21 | @property (nonatomic) L4Level * threshold; 22 | 23 | /** The layout used by this appender.*/ 24 | @property (atomic, strong) L4Layout * layout; 25 | 26 | /** The name for this appender.*/ 27 | @property (atomic, strong) NSString * name; 28 | 29 | /** 30 | * Initializes an instance from properties. 31 | * Refer to the L4PropertyConfigurator class for more information about standard configuration properties. 32 | * @param initProperties the proterties to use. 33 | */ 34 | - (id)initWithProperties:(L4Properties *)initProperties; 35 | 36 | /** 37 | * Appends an event to the log. 38 | * @param anEvent the event to be appended. 39 | */ 40 | - (void)append:(L4LogEvent *)anEvent; 41 | 42 | /** 43 | * Used to determine if a given event would be logged by this appender 44 | * given this appensers current threshold. 45 | * @param aLevel the level to be tested. 46 | * @return YES if this appended would log, NO otherwise. 47 | */ 48 | - (BOOL)isAsSevereAsThreshold:(L4Level *)aLevel; 49 | 50 | @end 51 | 52 | 53 | -------------------------------------------------------------------------------- /Log4Cocoa/L4AppenderSkeleton.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4AppenderSkeleton.h" 6 | #import "L4Filter.h" 7 | #import "L4Level.h" 8 | #import "L4LogEvent.h" 9 | #import "L4LogLog.h" 10 | #import "L4Layout.h" 11 | #import "L4Properties.h" 12 | 13 | /** 14 | * Private methods. 15 | */ 16 | @interface L4AppenderSkeleton () 17 | 18 | /** 19 | * Returns a new subclass of L4Filter specified by the class name, configured with the supplied properties. 20 | * @param filterClassName the name of the L4Filter subclass to instantiate. 21 | * @param filterProperties the properties used to configure the new instance. 22 | */ 23 | - (L4Filter *)filterForClassName:(NSString *)filterClassName andProperties:(L4Properties *)filterProperties; 24 | 25 | /** 26 | * Returns a new subclass of L4Layout specified by the class name, configured with the supplied properties. 27 | * @param layoutClassName the name of the L4Layout subclass to instantiate. 28 | * @param layoutProperties the properties used to configure the new instance. 29 | */ 30 | - (L4Layout *)layoutForClassName:(NSString *)layoutClassName andProperties:(L4Properties *)layoutProperties; 31 | 32 | @end 33 | 34 | @implementation L4AppenderSkeleton 35 | 36 | - (id)initWithProperties:(L4Properties *)initProperties 37 | { 38 | self = [super init]; 39 | 40 | if (self) { 41 | // Configure the layout 42 | if ([initProperties stringForKey:@"layout"]) { 43 | L4Properties *layoutProperties = [initProperties subsetForPrefix:@"layout."]; 44 | NSString *className = [initProperties stringForKey:@"layout"]; 45 | L4Layout *newLayout = [self layoutForClassName:className andProperties:layoutProperties]; 46 | 47 | if (!newLayout) { 48 | self.layout = newLayout; 49 | } else { 50 | [L4LogLog error:[NSString stringWithFormat:@"Error while creating layout \"%@\".", className]]; 51 | return nil; 52 | } 53 | } 54 | 55 | // Support for appender.Threshold in properties configuration file 56 | if ([initProperties stringForKey:@"Threshold"]) { 57 | NSString *newThreshold = [[initProperties stringForKey:@"Threshold"] uppercaseString]; 58 | [self setThreshold:[L4Level levelWithName:newThreshold]]; 59 | } 60 | 61 | // Configure the filters 62 | L4Properties *filtersProperties = [initProperties subsetForPrefix:@"filters."]; 63 | int filterCount = 0; 64 | while ([filtersProperties stringForKey:[@(++filterCount) stringValue]]) { 65 | NSString *filterName = [@(filterCount) stringValue]; 66 | L4Properties *filterProperties = [filtersProperties subsetForPrefix:[filterName stringByAppendingString:@"."]]; 67 | NSString *className = [filtersProperties stringForKey:filterName]; 68 | L4Filter *newFilter = [self filterForClassName:className andProperties:filterProperties]; 69 | 70 | if (newFilter) { 71 | [self appendFilter:newFilter]; 72 | } else { 73 | [L4LogLog error:[NSString stringWithFormat:@"Error while creating filter \"%@\".", className]]; 74 | return nil; 75 | } 76 | } 77 | } 78 | 79 | return self; 80 | } 81 | 82 | 83 | - (void)append:(L4LogEvent *)anEvent 84 | { 85 | } 86 | 87 | - (BOOL)isAsSevereAsThreshold:(L4Level *)aLevel 88 | { 89 | @synchronized(self) { 90 | return ((_threshold == nil) || ([aLevel isGreaterOrEqual:_threshold])); 91 | } 92 | } 93 | 94 | #pragma mark - Private methods 95 | 96 | - (L4Filter *)filterForClassName:(NSString *)filterClassName andProperties:(L4Properties *)filterProperties 97 | { 98 | L4Filter *newFilter = nil; 99 | Class filterClass = NSClassFromString(filterClassName); 100 | 101 | if ( filterClass == nil ) { 102 | [L4LogLog error:[NSString stringWithFormat:@"Cannot find L4Filter class with name:\"%@\".", filterClassName]]; 103 | } else { 104 | if ( ![[[filterClass alloc] init] isKindOfClass:[L4Filter class]] ) { 105 | [L4LogLog error:[NSString stringWithFormat:@"Failed to create instance with name \"%@\" since it is not of kind L4Filter.", filterClass]]; 106 | } else { 107 | newFilter = [(L4Filter *)[filterClass alloc] initWithProperties:filterProperties]; 108 | } 109 | } 110 | return newFilter; 111 | } 112 | 113 | - (L4Layout *)layoutForClassName:(NSString *)layoutClassName andProperties:(L4Properties *)layoutProperties 114 | { 115 | L4Layout *newLayout = nil; 116 | Class layoutClass = NSClassFromString(layoutClassName); 117 | 118 | if ( layoutClass == nil ) { 119 | [L4LogLog error:[NSString stringWithFormat:@"Cannot find L4Layout class with name:\"%@\".", layoutClassName]]; 120 | } else { 121 | if ( ![[[layoutClass alloc] init] isKindOfClass:[L4Layout class]] ) { 122 | [L4LogLog error: 123 | [NSString stringWithFormat: 124 | @"Failed to create instance with name \"%@\" since it is not of kind L4Layout.", layoutClass]]; 125 | } else { 126 | newLayout = [(L4Layout *)[layoutClass alloc] initWithProperties:layoutProperties]; 127 | } 128 | } 129 | return newLayout; 130 | } 131 | 132 | #pragma mark - L4AppenderCategory methods 133 | 134 | // calls [self append:anEvent] after doing threshold checks 135 | - (void)doAppend:(L4LogEvent *)anEvent 136 | { 137 | L4Filter *aFilter = [self headFilter]; 138 | BOOL breakLoop = NO; 139 | 140 | if(![self isAsSevereAsThreshold:[anEvent level]]) { 141 | return; 142 | } 143 | 144 | BOOL isOkToAppend = YES; 145 | 146 | @synchronized(self) { 147 | 148 | if (_closed) { 149 | [L4LogLog error:[NSString stringWithFormat:@"Attempted to append to closed appender named: %@", _name]]; 150 | isOkToAppend = NO; 151 | } 152 | 153 | while (aFilter && !breakLoop) { 154 | switch([aFilter decide:anEvent]) { 155 | case L4FilterDeny: 156 | isOkToAppend = NO; 157 | breakLoop = YES; 158 | break; 159 | case L4FilterAccept: 160 | breakLoop = YES; 161 | break; 162 | case L4FilterNeutral: 163 | default: 164 | aFilter = [aFilter next]; 165 | break; 166 | } 167 | } 168 | 169 | if (isOkToAppend) { 170 | [self append:anEvent]; // passed all threshold checks, append event. 171 | } 172 | } 173 | } 174 | 175 | - (void)appendFilter:(L4Filter *)newFilter 176 | { 177 | @synchronized(self) { 178 | if (!_headFilter) { 179 | _headFilter = _tailFilter = newFilter; 180 | } else { 181 | _tailFilter.next = newFilter; 182 | _tailFilter = newFilter; 183 | } 184 | } 185 | } 186 | 187 | - (L4Filter *)headFilter 188 | { 189 | return _headFilter; 190 | } 191 | 192 | - (void)clearFilters 193 | { 194 | @autoreleasepool { 195 | 196 | @synchronized(self) { 197 | for (L4Filter *filter = _headFilter; filter; filter = [_headFilter next] ) { 198 | filter.next = nil; 199 | } 200 | _headFilter = nil; 201 | _tailFilter = nil; 202 | } 203 | 204 | } 205 | } 206 | 207 | - (void)close 208 | { 209 | } 210 | 211 | - (BOOL)requiresLayout 212 | { 213 | return NO; 214 | } 215 | 216 | @end 217 | -------------------------------------------------------------------------------- /Log4Cocoa/L4BasicConfigurator.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "L4PropertyConfigurator.h" 3 | 4 | /** 5 | * Use this class to quickly configure the package. For file based 6 | * configuration see L4PropertyConfigurator. 7 | */ 8 | @interface L4BasicConfigurator : L4PropertyConfigurator 9 | 10 | /** 11 | * Factory method to return an instance of the default, basic configurator. 12 | */ 13 | + (L4BasicConfigurator *) basicConfigurator; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Log4Cocoa/L4BasicConfigurator.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4BasicConfigurator.h" 6 | #import "L4Properties.h" 7 | 8 | @implementation L4BasicConfigurator 9 | 10 | + (L4BasicConfigurator *)basicConfigurator 11 | { 12 | return [[L4BasicConfigurator alloc] init]; 13 | } 14 | 15 | - (id) init 16 | { 17 | self = [super initWithFileName:@""]; 18 | 19 | if (self) { 20 | [properties setString:@"DEBUG, STDOUT" forKey:@"rootLogger"]; 21 | [properties setString:@"L4ConsoleAppender" forKey:@"appender.STDOUT"]; 22 | [properties setString:@"L4SimpleLayout" forKey:@"appender.STDOUT.layout"]; 23 | } 24 | 25 | return self; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Log4Cocoa/L4ConsoleAppender.h: -------------------------------------------------------------------------------- 1 | // For copyright & license, see LICENSE. 2 | 3 | #import 4 | #import "L4WriterAppender.h" 5 | 6 | /** 7 | * An implementation of L4Appender that writes messages to the console; either stdout or stderr. 8 | */ 9 | @interface L4ConsoleAppender : L4WriterAppender 10 | 11 | /** 12 | * Creates and returns an L4ConsoleAppender on stdout with the given L4Layout. 13 | * @param aLayout the layout to use for the created appender. 14 | * @return the new appender. 15 | */ 16 | + (L4ConsoleAppender *) standardOutWithLayout:(L4Layout *) aLayout; 17 | 18 | /** 19 | * Creates and returns an L4ConsoleAppender on stderr with the given L4Layout. 20 | * @param aLayout the layout to use for the created appender. 21 | * @return the new appender. 22 | */ 23 | + (L4ConsoleAppender *) standardErrWithLayout:(L4Layout *) aLayout; 24 | 25 | /** 26 | * default init method. Returned instance isn't connected to anything. 27 | * @return the new instance. 28 | */ 29 | - (id) init; 30 | 31 | /** 32 | * Creates and returns a new console appender. 33 | * @param standardOut YES to use stdout; otherwise stderr is used. 34 | * @param aLayout the layout to use. 35 | * @return the new instance. 36 | */ 37 | - (id) initTarget:(BOOL) standardOut withLayout:(L4Layout *) aLayout; 38 | 39 | 40 | /** 41 | * Initializes an instance from properties. The properties supported are: 42 | * - LogToStandardOut: specifies if this appender should append to stdout or stderr. If the value is true, then 43 | * stdout will be used. Otherwise stderr will be used. 44 | * If the values are being set in a file, this is how they could look: 45 | * log4cocoa.appender.A2.LogToStandardOut=false 46 | * @param initProperties the proterties to use. 47 | */ 48 | - (id) initWithProperties:(L4Properties *) initProperties; 49 | 50 | /** 51 | * Accesor for isStandardOut attribute. 52 | * @return YES if this appender is for stdout; NO if it is for stderr. 53 | */ 54 | - (BOOL) isStandardOut; 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /Log4Cocoa/L4ConsoleAppender.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4ConsoleAppender.h" 6 | #import "L4Layout.h" 7 | #import "L4Properties.h" 8 | 9 | @implementation L4ConsoleAppender { 10 | BOOL _isStandardOut; /**< Tracks if this appender is for stdout.*/ 11 | } 12 | 13 | 14 | #pragma mark - Class methods 15 | 16 | + (L4ConsoleAppender *) standardOutWithLayout:(L4Layout *)aLayout 17 | { 18 | return [[L4ConsoleAppender alloc] initTarget:YES withLayout:aLayout]; 19 | } 20 | 21 | + (L4ConsoleAppender *) standardErrWithLayout:(L4Layout *)aLayout 22 | { 23 | return [[L4ConsoleAppender alloc] initTarget:NO withLayout:aLayout]; 24 | } 25 | 26 | 27 | #pragma mark - Instance methods 28 | 29 | - (id) init 30 | { 31 | return [self initTarget:YES withLayout:[L4Layout simpleLayout]]; 32 | } 33 | 34 | - (id) initTarget:(BOOL)standardOut withLayout:(L4Layout *)aLayout 35 | { 36 | self = [super init]; 37 | if (self) { 38 | if (standardOut) { 39 | [self setStandardOut]; 40 | } else { 41 | [self setStandardErr]; 42 | } 43 | self.layout = aLayout; 44 | } 45 | return self; 46 | } 47 | 48 | - (BOOL) isStandardOut 49 | { 50 | return _isStandardOut; 51 | } 52 | 53 | 54 | #pragma mark - L4Appender protocol methods 55 | 56 | - (id) initWithProperties:(L4Properties *)initProperties 57 | { 58 | self = [super initWithProperties:initProperties]; 59 | if (self) { 60 | BOOL logToStandardOut = YES; 61 | 62 | // Support for appender.LogToStandardOut in properties configuration file 63 | if ([initProperties stringForKey:@"LogToStandardOut"]) { 64 | NSString *buf = [[initProperties stringForKey:@"LogToStandardOut"] lowercaseString]; 65 | logToStandardOut = [buf isEqualToString:@"true"]; 66 | } 67 | 68 | if( logToStandardOut ) { 69 | [self setStandardOut]; 70 | } else { 71 | [self setStandardErr]; 72 | } 73 | } 74 | 75 | return self; 76 | } 77 | 78 | 79 | #pragma mark - Private methods 80 | 81 | - (void) setStandardOut 82 | { 83 | @synchronized(self) { 84 | if (!_isStandardOut || !_fileHandle) { 85 | _isStandardOut = YES; 86 | _fileHandle = [NSFileHandle fileHandleWithStandardOutput]; 87 | } 88 | } 89 | } 90 | 91 | - (void) setStandardErr 92 | { 93 | @synchronized(self) { 94 | if (_isStandardOut || !_fileHandle) { 95 | _isStandardOut = NO; 96 | _fileHandle = [NSFileHandle fileHandleWithStandardError]; 97 | } 98 | } 99 | } 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /Log4Cocoa/L4DailyRollingFileAppender.h: -------------------------------------------------------------------------------- 1 | // For copyright & license, see LICENSE. 2 | 3 | #import 4 | #import "L4FileAppender.h" 5 | 6 | /** 7 | * The accepted constants for L4DailyRollingFileAppenderFrequency's setFrequency: method. 8 | */ 9 | extern NSString *const L4RollingFrequencyKey; 10 | 11 | extern NSString *const L4RollingFrequencyNever; /**< Never roll the file. */ 12 | extern NSString *const L4RollingFrequencyMonthly; /**< Roll the file over every month. */ 13 | extern NSString *const L4RollingFrequencyWeekly; /**< Roll the file over every week. */ 14 | extern NSString *const L4RollingFrequencyDaily; /**< Roll the file over every day. */ 15 | extern NSString *const L4RollingFrequencyHalfDaily; /**< Roll the file over every 12 hours. */ 16 | extern NSString *const L4RollingFrequencyHourly; /**< Roll the file over every hour. */ 17 | extern NSString *const L4RollingFrequencyMinutely; /**< Roll the file over every minute. */ 18 | 19 | /** 20 | * L4DailyRollingFileAppender extends L4FileAppender so that the underlying file is rolled over at a 21 | * user-chosen frequency. For example, if the fileName is set to /foo/bar.log and the frequency is set 22 | * to daily, on 2001-02-16 at midnight, the logging file /foo/bar.log will be copied to /foo/bar.log.2001-02-16 23 | * and logging for 2001-02-17 will continue in /foo/bar.log until it rolls over the next day. 24 | * It is possible to specify monthly, weekly, half-daily, daily, hourly, or minutely rollover schedules. 25 | */ 26 | @interface L4DailyRollingFileAppender : L4FileAppender { 27 | NSDate *_lastRolloverDate; /**< The date the last role-over ocured.*/ 28 | } 29 | 30 | /** The frequency with which the file should be rolled.*/ 31 | @property (nonatomic, copy) NSString *rollingFrequency; 32 | 33 | /** 34 | * This initializer calls the initWithLayout:fileName:rollingFrequency: method with the respective values:nil, nil, never. 35 | */ 36 | - (id)init; 37 | 38 | /** 39 | * Initializes an instance of this class with the specified layout, file name, and rolling frequency. 40 | * @param aLayout The layout object for this appender 41 | * @param aName The file path to the file in which you want logging output recorded 42 | * @param aRollingFrequency The frequency at which you want the log file rolled over 43 | */ 44 | - (id)initWithLayout:(L4Layout*)aLayout fileName:(NSString*)aName rollingFrequency:(NSString *)aRollingFrequency; 45 | 46 | /** 47 | * Initializes an instance from properties. The properties supported are: 48 | * - L4RollingFrequencyKey: specifies the frequency when the log file should be rolled. See L4RollingFrequency. 49 | * If the values are being set in a file, this is how they could look: 50 | * log4cocoa.appender.A2.L4RollingFrequency=L4RollingFrequencyDaily 51 | * @param initProperties the proterties to use. 52 | */ 53 | - (id) initWithProperties:(L4Properties *) initProperties; 54 | 55 | @end 56 | 57 | /** 58 | * These methods are "protected" methods and should not be called except by subclasses. 59 | */ 60 | @interface L4DailyRollingFileAppender (ProtectedMethods) 61 | 62 | /** 63 | This method overrides the implementation in L4WriterAppender. It checks if the rolling frequency has been exceeded. 64 | * If so, it rolls the file over. 65 | * @param event An L4LoggingEvent that contains logging specific information. 66 | */ 67 | - (void)subAppend: (L4LogEvent*)event; 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /Log4Cocoa/L4DailyRollingFileAppender.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4DailyRollingFileAppender.h" 6 | #import "L4Layout.h" 7 | #import "L4LogLog.h" 8 | #import "L4Properties.h" 9 | 10 | #import 11 | 12 | NSString *const L4RollingFrequencyKey = @"L4RollingFrequency"; 13 | 14 | NSString *const L4RollingFrequencyNever = @"L4RollingFrequencyNever"; 15 | NSString *const L4RollingFrequencyMonthly = @"L4RollingFrequencyMonthly"; 16 | NSString *const L4RollingFrequencyWeekly = @"L4RollingFrequencyWeekly"; 17 | NSString *const L4RollingFrequencyDaily = @"L4RollingFrequencyDaily"; 18 | NSString *const L4RollingFrequencyHalfDaily = @"L4RollingFrequencyHalfDaily"; 19 | NSString *const L4RollingFrequencyHourly = @"L4RollingFrequencyHourly"; 20 | NSString *const L4RollingFrequencyMinutely = @"L4RollingFrequencyMinutely"; 21 | 22 | /** 23 | * Private methods for the L4DailyRollingFileAppender class. 24 | */ 25 | @interface L4DailyRollingFileAppender () 26 | /** 27 | * Accessor for the lastRolloverDate property. 28 | */ 29 | - (NSDate *)lastRolloverDate; 30 | /** 31 | * Mutator for the lastRolloverDate property. 32 | */ 33 | - (void)setLastRolloverDate:(NSDate*)date; 34 | 35 | /** 36 | * Causes a new log file to be generated. 37 | */ 38 | - (void)rollOver; 39 | 40 | @end 41 | 42 | @implementation L4DailyRollingFileAppender 43 | 44 | - (id)init 45 | { 46 | return [self initWithLayout:nil fileName:nil rollingFrequency:L4RollingFrequencyNever]; 47 | } 48 | 49 | - (id)initWithLayout:(L4Layout*)aLayout fileName:(NSString*)aName rollingFrequency:(NSString *)aRollingFrequency 50 | { 51 | self = [super initWithLayout:aLayout fileName:aName append:YES]; 52 | 53 | if (self != nil) { 54 | [self setRollingFrequency:aRollingFrequency]; 55 | } 56 | 57 | return self; 58 | } 59 | 60 | - (void)setRollingFrequency:(NSString *)aRollingFrequency 61 | { 62 | _rollingFrequency = [aRollingFrequency copy]; 63 | [self setLastRolloverDate:[NSDate date]]; 64 | } 65 | 66 | /* ********************************************************************* */ 67 | #pragma mark L4PropertiesConfigurable protocol methods 68 | /* ********************************************************************* */ 69 | - (id) initWithProperties:(L4Properties *) initProperties 70 | { 71 | self = [super initWithProperties:initProperties]; 72 | 73 | if (self) { 74 | // Support for appender.L4RollingFrequency in properties configuration file 75 | NSString *rollingFrequency = [initProperties stringForKey:L4RollingFrequencyKey]; 76 | if (rollingFrequency) { 77 | self.rollingFrequency = rollingFrequency; 78 | } 79 | } 80 | 81 | return self; 82 | } 83 | 84 | /* ********************************************************************* */ 85 | #pragma mark Protected methods 86 | /* ********************************************************************* */ 87 | - (void)subAppend:(L4LogEvent*)event 88 | { 89 | [self rollOver]; 90 | [super subAppend:event]; 91 | } 92 | 93 | /* ********************************************************************* */ 94 | #pragma mark Private methods 95 | /* ********************************************************************* */ 96 | - (NSDate*)lastRolloverDate 97 | { 98 | return _lastRolloverDate; 99 | } 100 | 101 | - (void)setLastRolloverDate:(NSDate*)date 102 | { 103 | @synchronized(self) { 104 | if (_lastRolloverDate != date) { 105 | _lastRolloverDate = date; 106 | } 107 | } 108 | } 109 | 110 | /** 111 | * Rolls file name 112 | */ 113 | - (void)rollOver 114 | { 115 | // if the rolling frequency is never, return 116 | if ([self.rollingFrequency isEqualToString:L4RollingFrequencyNever]) 117 | return; 118 | 119 | @synchronized(self) { 120 | 121 | NSDate *now = [NSDate date]; 122 | 123 | NSCalendar *calendar = [NSCalendar currentCalendar]; 124 | NSDateComponents *nowDateComponents = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSWeekCalendarUnit 125 | | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit) 126 | fromDate:now]; 127 | 128 | NSDateComponents *lastDateComponents = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSWeekCalendarUnit 129 | | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit) 130 | fromDate:self.lastRolloverDate]; 131 | 132 | bool doRollover = NO; 133 | NSString *format; 134 | 135 | if ([self.rollingFrequency isEqualToString:L4RollingFrequencyMonthly]) { 136 | doRollover = ([nowDateComponents year] != [lastDateComponents year]) 137 | || ([nowDateComponents month] != [lastDateComponents month]); 138 | 139 | format = @"yyyy"; 140 | 141 | } else if ([self.rollingFrequency isEqualToString:L4RollingFrequencyWeekly]) { 142 | doRollover = ([nowDateComponents year] != [lastDateComponents year]) 143 | || ([nowDateComponents week] != [lastDateComponents week]); 144 | 145 | format = @"yyyy-MM"; 146 | 147 | } else if ([self.rollingFrequency isEqualToString:L4RollingFrequencyDaily]) { 148 | doRollover = ([nowDateComponents year] != [lastDateComponents year]) 149 | || ([nowDateComponents month] != [lastDateComponents month]) 150 | || ([nowDateComponents day] != [lastDateComponents day]); 151 | 152 | format = @"yyyy-MM-dd"; 153 | 154 | } else if ([self.rollingFrequency isEqualToString:L4RollingFrequencyHalfDaily]) { 155 | doRollover = ([nowDateComponents year] != [lastDateComponents year]) 156 | || ([nowDateComponents month] != [lastDateComponents month]) 157 | || ([nowDateComponents day] != [lastDateComponents day]) 158 | || (([nowDateComponents hour] / 12) != ([lastDateComponents hour] / 12)); 159 | 160 | format = @"yyyy-MM-dd-HH"; 161 | 162 | } else if ([self.rollingFrequency isEqualToString:L4RollingFrequencyHourly]) { 163 | doRollover = ([nowDateComponents year] != [lastDateComponents year]) 164 | || ([nowDateComponents month] != [lastDateComponents month]) 165 | || ([nowDateComponents day] != [lastDateComponents day]) 166 | || ([nowDateComponents hour] != [lastDateComponents hour]); 167 | 168 | format = @"yyyy-MM-dd-HH"; 169 | 170 | } else if ([self.rollingFrequency isEqualToString:L4RollingFrequencyMinutely]) { 171 | doRollover = ([nowDateComponents year] != [lastDateComponents year]) 172 | || ([nowDateComponents month] != [lastDateComponents month]) 173 | || ([nowDateComponents day] != [lastDateComponents day]) 174 | || ([nowDateComponents hour] != [lastDateComponents hour]) 175 | || ([nowDateComponents minute] != [lastDateComponents minute]); 176 | 177 | format = @"yyyy-MM-dd-HH-mm"; 178 | } 179 | 180 | if (doRollover) { 181 | // generate the new filename extension 182 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 183 | dateFormatter.dateFormat = format; 184 | NSString *filenameExtension = [dateFormatter stringFromDate:[self lastRolloverDate]]; 185 | 186 | // generate the new rollover log file name 187 | NSString *newFileName = [[self fileName] stringByAppendingPathExtension:filenameExtension]; 188 | 189 | // close the current log file 190 | [self closeFile]; 191 | 192 | // rename the current log file to the new rollover log file name 193 | NSFileManager *fileManager = [NSFileManager defaultManager]; 194 | if (![fileManager moveItemAtPath:[self fileName] toPath:newFileName error:nil]) { 195 | // if we can't rename the file, log an error 196 | [L4LogLog error:[NSString stringWithFormat:@"Unable to move file from %@ to %@", [self fileName], newFileName]]; 197 | } 198 | 199 | // re-activate this appender (this will open a new log file named [self fileName]) 200 | [self setupFile]; 201 | 202 | [self setLastRolloverDate:now]; 203 | } 204 | 205 | } 206 | } 207 | @end 208 | -------------------------------------------------------------------------------- /Log4Cocoa/L4DenyAllFilter.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "L4Filter.h" 3 | 4 | /** 5 | * 6 | * This filter drops all logging events. 7 | * 8 | * You can add this filter to the end of a filter chain to switch from the default "accept all unless instructed 9 | * otherwise" filtering behaviour to a "deny all unless instructed otherwise" behaviour. 10 | */ 11 | @interface L4DenyAllFilter : L4Filter 12 | 13 | @end 14 | // For copyright & license, see LICENSE. 15 | -------------------------------------------------------------------------------- /Log4Cocoa/L4DenyAllFilter.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4DenyAllFilter.h" 6 | 7 | 8 | @implementation L4DenyAllFilter 9 | 10 | - (L4FilterResult) decide:(L4LogEvent *) event 11 | { 12 | return L4FilterDeny; 13 | } 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Log4Cocoa/L4FileAppender.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This appender appends logging messages to a file whose path you specify. This class is a subclass of L4WriterAppender. 3 | * The L4FileAppender does not support buffering configuration. Any methods or arguments that refer to buffering are ignored. 4 | * For copyright & license, see LICENSE. 5 | */ 6 | 7 | #import 8 | #import "L4WriterAppender.h" 9 | 10 | @interface L4FileAppender : L4WriterAppender 11 | 12 | /** 13 | * Tracks if we should append or over-right. 14 | */ 15 | 16 | @property (nonatomic, readonly) BOOL append; 17 | 18 | /** 19 | * The path to the file to which log output should be written. 20 | */ 21 | @property (nonatomic, readonly) NSString * fileName; 22 | 23 | /** 24 | * A basic initializer. 25 | * This method calls initWithLayout:fileName:append:bufferIO:bufferSize: with layout and file name set to nil, 26 | * append is NO, bufferIO is NO, bufferSize is 0 27 | */ 28 | - (id)init; 29 | 30 | /** 31 | * Initializes an instance from properties. The properties supported are: 32 | * - File: the full path of the file to use. 33 | * - Append: weather to append to the file, or create a new one. Expected values are [true|false]. 34 | * If the values are being set in a file, this is how they could look: 35 | * log4cocoa.appender.A2.File=/tmp/mylogfile.txt 36 | * log4cocoa.appender.A2.Append=false 37 | * @param initProperties the proterties to use. 38 | */ 39 | - (id)initWithProperties:(L4Properties *)initProperties; 40 | 41 | /** 42 | * Initializes an L4FileAppender instance with the specified layout and file path name. 43 | * This method calls initWithLayout:fileName:append:bufferIO:bufferSize: with the specified layout and file name, 44 | * append is NO, bufferIO is NO, bufferSize is 0 45 | * @param aLayout The layout that this appender should use 46 | * @param aName The file path of the file you want log output to be written to. If the file does not exist, it will be created if possible. If the file cannot be created for some reason, a FileNotFoundException will be raised. 47 | * @throws 48 | * @return An initialized L4FileAppender object 49 | */ 50 | - (id)initWithLayout:(L4Layout *)aLayout fileName:(NSString *)aName; 51 | 52 | /** 53 | * Initializes an L4FileAppender instance with the specified layout, file path name, and append option. 54 | * This method calls initWithLayout:fileName:append:bufferIO:bufferSize: with the specified layout, file name, and append 55 | * option, bufferIO is NO, bufferSize is 0 56 | * @param aLayout The layout that this appender should use 57 | * @param aName The file path of the file you want log output to be written to. If the file does not exist, it will be created if possible. If the file cannot be created for some reason, a FileNotFoundException will be raised. 58 | * @param flag YES = log output should be appended to the file. NO = the file's previous contents should be overwritten 59 | * @throws 60 | * @return An initialized L4FileAppender object 61 | */ 62 | - (id)initWithLayout:(L4Layout *)aLayout fileName:(NSString *)aName append:(BOOL)flag; 63 | 64 | @end 65 | 66 | /** 67 | * These methods are "protected" methods and should not be called except by subclasses. 68 | */ 69 | @interface L4FileAppender (ProtectedMethods) 70 | 71 | /** 72 | * This method closes and releases the underlying file handle. 73 | */ 74 | - (void)closeFile; 75 | 76 | /** 77 | * This method is called to insure the file is set up to write to. 78 | */ 79 | - (void)setupFile; 80 | 81 | 82 | @end 83 | // For copyright & license, see LICENSE. 84 | -------------------------------------------------------------------------------- /Log4Cocoa/L4FileAppender.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4FileAppender.h" 6 | #import "L4Layout.h" 7 | #import "L4LogLog.h" 8 | #import "L4Properties.h" 9 | 10 | @implementation L4FileAppender 11 | 12 | - (id) init 13 | { 14 | return [self initWithLayout:nil fileName:nil append:NO]; 15 | } 16 | 17 | - (id) initWithProperties:(L4Properties *) initProperties 18 | { 19 | self = [super initWithProperties:initProperties]; 20 | 21 | if (self) { 22 | // Support for appender.File in properties configuration file 23 | NSString *buf = [initProperties stringForKey:@"File"]; 24 | if (!buf) { 25 | [L4LogLog error:@"Invalid filename; L4FileAppender properties require a file be specified."]; 26 | return nil; 27 | } 28 | _fileName = [buf stringByExpandingTildeInPath]; 29 | 30 | // Support for appender.Append in properties configuration file 31 | _append = YES; 32 | if ([initProperties stringForKey:@"Append"]) { 33 | NSString *buf = [[initProperties stringForKey:@"Append"] lowercaseString]; 34 | _append = [buf isEqualToString:@"true"]; 35 | } 36 | [self setupFile]; 37 | } 38 | 39 | return self; 40 | } 41 | 42 | - (id) initWithLayout:(L4Layout *) aLayout fileName:(NSString *) aName 43 | { 44 | return [self initWithLayout:aLayout fileName:aName append:NO]; 45 | } 46 | 47 | - (id) initWithLayout:(L4Layout *) aLayout fileName:(NSString *) aName append:(BOOL) flag 48 | { 49 | self = [super init]; 50 | if (self != nil) 51 | { 52 | [self setLayout:aLayout]; 53 | _fileName = [aName stringByExpandingTildeInPath]; 54 | _append = flag; 55 | [self setupFile]; 56 | } 57 | return self; 58 | } 59 | 60 | - (void)setupFile 61 | { 62 | NSFileManager* fileManager = nil; 63 | 64 | @synchronized(self) { 65 | if (!_fileName || _fileName.length == 0) { 66 | [self closeFile]; 67 | _fileName = nil; 68 | [self setFileHandle:nil]; 69 | } else { 70 | 71 | fileManager = [NSFileManager defaultManager]; 72 | 73 | // if file doesn't exist, try to create the file 74 | if (![fileManager fileExistsAtPath:_fileName]) { 75 | // if the we cannot create the file, raise a FileNotFoundException 76 | if (![fileManager createFileAtPath:_fileName contents:nil attributes:nil]) { 77 | [NSException raise:@"FileNotFoundException" format:@"Couldn't create a file at %@", _fileName]; 78 | } 79 | } 80 | 81 | // if we had a previous file name, close it and release the file handle 82 | if (_fileName) 83 | [self closeFile]; 84 | 85 | // open a file handle to the file 86 | [self setFileHandle:[NSFileHandle fileHandleForWritingAtPath:_fileName]]; 87 | 88 | // check the append option 89 | if (_append) 90 | [_fileHandle seekToEndOfFile]; 91 | else 92 | [_fileHandle truncateFileAtOffset:0]; 93 | } 94 | } 95 | } 96 | 97 | 98 | #pragma mark - Protected methods 99 | 100 | - (void) closeFile 101 | { 102 | @synchronized(self) { 103 | [_fileHandle closeFile]; 104 | 105 | // Deallocate the file handle because trying to read from or write to a closed file raises exceptions. Sending messages to nil objects are no-ops. 106 | _fileHandle = nil; 107 | } 108 | } 109 | @end 110 | 111 | -------------------------------------------------------------------------------- /Log4Cocoa/L4Filter.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "L4AppenderProtocols.h" 3 | 4 | @class L4LogEvent, L4Properties; 5 | 6 | /** 7 | * An enumeration of the allowed L4Filter actions. 8 | */ 9 | typedef NS_ENUM(NSUInteger, L4FilterResult) { 10 | L4FilterDeny = -1, /**< Prevent the request to log the event. */ 11 | L4FilterNeutral = 0, /**< Do not affect the request to log the event. */ 12 | L4FilterAccept = 1 /**< Allow the request to log he event. */ 13 | }; 14 | 15 | /** 16 | * Filter for log events. 17 | * This class is intended to serve as the base for filters. This class itself does nothing; 18 | * the only way a flter would actually be used would be to subclass it and over-ride the 19 | * decide:method. 20 | */ 21 | @interface L4Filter : NSObject 22 | 23 | @property (strong) L4Filter * next; /**< Accessor for the next filter. */ 24 | 25 | /** 26 | * Initializes an instance from properties. Currently there are no properties that apply to this class. 27 | * @param initProperties the proterties to use. 28 | * @throw L4PropertyMissingException if a required property is missing. 29 | */ 30 | - (id)initWithProperties:(L4Properties *)initProperties; 31 | 32 | /** 33 | * Decide what this filter should do with event. 34 | * This method is used to determine if the event should be logged. 35 | * @param event the event to check. 36 | * @return one of L4FilterDeny, L4FilterNeutral, or L4FilterAccept. 37 | */ 38 | - (L4FilterResult)decide:(L4LogEvent *)event; 39 | 40 | @end 41 | // For copyright & license, see LICENSE. 42 | -------------------------------------------------------------------------------- /Log4Cocoa/L4Filter.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4Filter.h" 6 | #import "L4LogEvent.h" 7 | 8 | 9 | @implementation L4Filter 10 | 11 | - (id)initWithProperties:(L4Properties *)initProperties 12 | { 13 | return [super init]; 14 | } 15 | 16 | 17 | - (L4FilterResult)decide:(L4LogEvent *)event 18 | { 19 | return L4FilterNeutral; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Log4Cocoa/L4JSONLayout.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "L4Layout.h" 4 | 5 | /** 6 | * This class represents formating a JSON object for error. 7 | */ 8 | @interface L4JSONLayout : L4Layout 9 | 10 | + (instancetype)JSONLayout; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Log4Cocoa/L4JSONLayout.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4JSONLayout.h" 6 | #import "L4LogEvent.h" 7 | #import "L4Properties.h" 8 | #import "L4Logger.h" 9 | 10 | @implementation L4JSONLayout 11 | 12 | + (instancetype)JSONLayout 13 | { 14 | return [[L4JSONLayout alloc] init]; 15 | } 16 | 17 | - (NSString *)format:(L4LogEvent *)event 18 | { 19 | /* 20 | { 21 | "logger": "%@" 22 | "level": "%@" 23 | "time": "%ldms" 24 | "file": "%@:%@" 25 | "method": "%@" 26 | "message": "%@" 27 | } 28 | */ 29 | return [NSString stringWithFormat: 30 | @"{\n" 31 | "t\"logger\":\"%@\",\n" 32 | "\t\"level\":\"%@\",\n" 33 | "\t\"time\":\"%ldms\",\n" 34 | "\t\"file\":\"%@:%@\",\n" 35 | "\t\"method\":\"%@\",\n" 36 | "\t\"message\":\"%@\"\n" 37 | "}\n", 38 | event.logger.name, 39 | event.level.stringValue, 40 | event.millisSinceStart, 41 | event.fileName, 42 | event.lineNumber.stringValue, 43 | event.methodName, 44 | event.renderedMessage]; 45 | } 46 | 47 | - (NSString *)contentType { 48 | return @"application/json"; 49 | } 50 | 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /Log4Cocoa/L4Layout.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "L4AppenderProtocols.h" 3 | 4 | @class L4LogEvent, L4SimpleLayout, L4Properties; 5 | 6 | /** 7 | * This class represents formating a line of log output. 8 | */ 9 | @interface L4Layout : NSObject 10 | 11 | /** 12 | * Create and return a simple layout. 13 | * This factory method is used to create a simple layout. 14 | * @return the new instance of L4SimpleLayout. 15 | */ 16 | + (instancetype)simpleLayout; 17 | 18 | /** 19 | * Initializes an instance from properties. Currently there are no properties that apply to this class. 20 | * @param initProperties the proterties to use. 21 | */ 22 | - (id)initWithProperties:(L4Properties *) initProperties; 23 | 24 | /** 25 | * Format a log event. 26 | * This method will format a given event based on our layout and return a string reasy for writing. 27 | * @param event the event to be formatted. 28 | * @return a string of the formatted event. 29 | */ 30 | - (NSString *)format:(L4LogEvent *) event; 31 | 32 | /** 33 | * The MIME type of the string returned by the format:method. 34 | * @return the MIME type. 35 | */ 36 | - (NSString *)contentType; 37 | 38 | /** 39 | * Any header content that should be written to the log. 40 | * @return the string header. 41 | */ 42 | - (NSString *)header; 43 | 44 | /** 45 | * Any footer content that should be written to the log. 46 | * @return the string footer. 47 | */ 48 | - (NSString *)footer; 49 | 50 | /** 51 | * Should this formatter format exceptions. 52 | * @return YES if this formatter should format an exception; NO if it should not. 53 | */ 54 | - (BOOL)ignoresExceptions; 55 | 56 | @end 57 | // For copyright & license, see LICENSE. 58 | -------------------------------------------------------------------------------- /Log4Cocoa/L4Layout.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4Layout.h" 6 | #import "L4SimpleLayout.h" 7 | #import "L4LogEvent.h" 8 | 9 | @implementation L4Layout 10 | 11 | + (instancetype)simpleLayout 12 | { 13 | return [[L4SimpleLayout alloc] init]; 14 | } 15 | 16 | - (id)initWithProperties:(L4Properties *)initProperties 17 | { 18 | return [super init]; 19 | } 20 | 21 | - (NSString *)format:(L4LogEvent *)event 22 | { 23 | return [event description]; 24 | } 25 | 26 | - (NSString *)contentType 27 | { 28 | return @"text/plain"; 29 | } 30 | 31 | - (NSString *)header 32 | { 33 | return nil; 34 | } 35 | 36 | - (NSString *)footer 37 | { 38 | return nil; 39 | } 40 | 41 | - (BOOL)ignoresExceptions 42 | { 43 | return YES; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Log4Cocoa/L4Level.h: -------------------------------------------------------------------------------- 1 | // For copyright & license, see LICENSE. 2 | 3 | #import 4 | 5 | // ALL < DEBUG < INFO < WARN < ERROR < FATAL < OFF 6 | 7 | #define OFF_INT 99 8 | #define FATAL_INT 60 9 | #define ERROR_INT 50 10 | #define WARN_INT 40 11 | #define INFO_INT 30 12 | #define DEBUG_INT 20 13 | #define TRACE_INT 10 14 | #define ALL_INT 0 15 | 16 | @interface L4Level : NSObject 17 | 18 | /** 19 | * The int value of this log level. 20 | */ 21 | @property (readonly) int intValue; 22 | 23 | /** 24 | * The name of this level. 25 | */ 26 | @property (readonly) NSString * name; 27 | 28 | /** 29 | * The int equivelent for syslog. 30 | */ 31 | @property (readonly) int syslogEquivalent; 32 | 33 | /** 34 | * Creates and returns an instance with the provided values. 35 | * @param aLevel the level for this instance. 36 | * @param aName the neame for this level. 37 | * @param sysLogLevel the system log level for this instance. 38 | * @return the new instance. 39 | */ 40 | + (L4Level *)level:(int)aLevel withName:(NSString *)aName syslogEquivalent:(int)sysLogLevel; 41 | 42 | /** 43 | * Accessor for the default instance with a level of off. 44 | * @return the off instance. 45 | */ 46 | + (L4Level *)off; 47 | 48 | /** 49 | * Accessor for the default instance with a level of fatal. 50 | * @return the fatal instance. 51 | */ 52 | + (L4Level *)fatal; 53 | 54 | /** 55 | * Accessor for the default instance with a level of error. 56 | * @return the error instance. 57 | */ 58 | + (L4Level *)error; 59 | 60 | /** 61 | * Accessor for the default instance with a level of warn. 62 | * @return the warn instance. 63 | */ 64 | + (L4Level *)warn; 65 | 66 | /** 67 | * Accessor for the default instance with a level of info. 68 | * @return the info instance. 69 | */ 70 | + (L4Level *)info; 71 | 72 | /** 73 | * Accessor for the default instance with a level of debug. 74 | * @return debug off instance. 75 | */ 76 | + (L4Level *)debug; 77 | 78 | /** 79 | * Accessor for the default instance with a level of trace. 80 | * @return trace off instance. 81 | */ 82 | + (L4Level *)trace; 83 | 84 | /** 85 | * Accessor for the default instance with a level of all. 86 | * @return the all instance. 87 | */ 88 | + (L4Level *)all; 89 | 90 | /** 91 | * Create and return a new instance where the level is set to the string argument. 92 | * @param aLevel the name of the level requested. The options are: 93 | * - ALL 94 | * - DEBUG 95 | * - INFO 96 | * - WARN 97 | * - ERROR 98 | * - FATAL 99 | * - OFF 100 | * @return the new L4Level. 101 | */ 102 | + (L4Level *)levelWithName:(NSString *)aLevel; 103 | 104 | /** 105 | * Create and return a new instance where the level is set to the string argument. If no level matches the 106 | * argument, the defaultLevel is rturned. 107 | * @param aLevel the name of the level requested. The options are: 108 | * - ALL 109 | * - DEBUG 110 | * - INFO 111 | * - WARN 112 | * - ERROR 113 | * - FATAL 114 | * - OFF 115 | * @param defaultLevel the level to use if aLevel can not be dereferenced. 116 | * @return the new L4Level. 117 | */ 118 | + (L4Level *)levelWithName:(NSString *)aLevel defaultLevel:(L4Level *)defaultLevel; 119 | 120 | /** 121 | * Create and return a new instance where the level is set to the int argument. 122 | * @param aLevel the int specifier of the level requested. 123 | * @return the new L4Level. 124 | */ 125 | + (L4Level *)levelWithInt:(int)aLevel; 126 | 127 | /** 128 | * Create and return a new instance where the level is set to the int argument. If no level matches the 129 | * argument, the defaultLevel is rturned. 130 | * @param aLevel the int specifier of the level requested. 131 | * @param defaultLevel the level to use if aLevel can not be dereferenced. 132 | * @return the new L4Level. 133 | */ 134 | + (L4Level *)levelWithInt:(int)aLevel defaultLevel:(L4Level *)defaultLevel; 135 | 136 | /** 137 | * Initializes a new instance with the parameters supplied. 138 | * @param aLevel 139 | * @param aName 140 | * @param sysLogLevel 141 | * @return the newly initialized instance. 142 | */ 143 | - (id)initLevel:(int)aLevel withName:(NSString *)aName syslogEquivalent:(int)sysLogLevel; 144 | 145 | /** 146 | * Used to access a textual representation of this level. 147 | * @return the string describing this instance. 148 | */ 149 | - (NSString *)description; 150 | 151 | /** 152 | * Used to access a string representation of this level. 153 | * @return the string describing this instance. 154 | */ 155 | - (NSString *)stringValue; 156 | 157 | /** 158 | * Used to determine if this instance is of greator or equal level to the argument. 159 | * @return YES if this instance is of at least equal level to he argument; NO if it is not. 160 | */ 161 | - (BOOL)isGreaterOrEqual:(L4Level *)aLevel; 162 | 163 | /** 164 | * Used to determine if this instance is to be logged based on the level of the parameter. 165 | * @param aLevel the level that is the minimum to be logged. 166 | * @return YES if this level should be logged; we must be greater than the parameter to be true; NO 167 | * if we ar eless. 168 | */ 169 | - (BOOL)isEnabledFor:(L4Level *)aLevel; 170 | 171 | @end 172 | -------------------------------------------------------------------------------- /Log4Cocoa/L4Level.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4Level.h" 6 | 7 | @implementation L4Level 8 | 9 | // L4_ALL < L4_TRACE < L4_DEBUG < L4_INFO < L4_WARN < L4_ERROR < L4_FATAL < L4_OFF 10 | 11 | static L4Level *L4_OFF = nil; 12 | static L4Level *L4_FATAL = nil; 13 | static L4Level *L4_ERROR = nil; 14 | static L4Level *L4_WARN = nil; 15 | static L4Level *L4_INFO = nil; 16 | static L4Level *L4_DEBUG = nil; 17 | static L4Level *L4_TRACE = nil; 18 | static L4Level *L4_ALL = nil; 19 | 20 | #pragma mark - Class methods 21 | 22 | + (void)initialize 23 | { 24 | L4_OFF = [L4Level level:OFF_INT withName:@"OFF" syslogEquivalent:0]; 25 | L4_FATAL = [L4Level level:FATAL_INT withName:@"FATAL" syslogEquivalent:0]; 26 | L4_ERROR = [L4Level level:ERROR_INT withName:@"ERROR" syslogEquivalent:3]; 27 | L4_WARN = [L4Level level:WARN_INT withName:@"WARN" syslogEquivalent:4]; 28 | L4_INFO = [L4Level level:INFO_INT withName:@"INFO" syslogEquivalent:6]; 29 | L4_DEBUG = [L4Level level:DEBUG_INT withName:@"DEBUG" syslogEquivalent:7]; 30 | L4_TRACE = [L4Level level:TRACE_INT withName:@"TRACE" syslogEquivalent:7]; 31 | L4_ALL = [L4Level level:ALL_INT withName:@"ALL" syslogEquivalent:7]; 32 | } 33 | 34 | + (L4Level *)level:(int)aLevel withName:(NSString *)aName syslogEquivalent:(int)sysLogLevel 35 | { 36 | return [[L4Level alloc] initLevel:aLevel withName:aName syslogEquivalent:sysLogLevel]; 37 | } 38 | 39 | + (L4Level *)off 40 | { 41 | return L4_OFF; 42 | } 43 | 44 | + (L4Level *)fatal 45 | { 46 | return L4_FATAL; 47 | } 48 | 49 | + (L4Level *)error 50 | { 51 | return L4_ERROR; 52 | } 53 | 54 | + (L4Level *)warn 55 | { 56 | return L4_WARN; 57 | } 58 | 59 | + (L4Level *)info 60 | { 61 | return L4_INFO; 62 | } 63 | 64 | + (L4Level *)debug 65 | { 66 | return L4_DEBUG; 67 | } 68 | 69 | + (L4Level *)trace 70 | { 71 | return L4_TRACE; 72 | } 73 | 74 | + (L4Level *)all 75 | { 76 | return L4_ALL; 77 | } 78 | 79 | + (L4Level *)levelWithName:(NSString *)aLevel 80 | { 81 | return [L4Level levelWithName:aLevel defaultLevel:L4_DEBUG]; 82 | } 83 | 84 | + (L4Level *)levelWithName:(NSString *)aLevel defaultLevel:(L4Level *)defaultLevel 85 | { 86 | NSString *theLevel; 87 | 88 | if (!aLevel) { return defaultLevel; } 89 | 90 | theLevel = [aLevel uppercaseString]; 91 | 92 | if ([theLevel isEqualToString:@"ALL"]) { return L4_ALL; } 93 | if ([theLevel isEqualToString:@"TRACE"]) { return L4_TRACE; } 94 | if ([theLevel isEqualToString:@"DEBUG"]) { return L4_DEBUG; } 95 | if ([theLevel isEqualToString:@"INFO"]) { return L4_INFO; } 96 | if ([theLevel isEqualToString:@"WARN"]) { return L4_WARN; } 97 | if ([theLevel isEqualToString:@"ERROR"]) { return L4_ERROR; } 98 | if ([theLevel isEqualToString:@"FATAL"]) { return L4_FATAL; } 99 | if ([theLevel isEqualToString:@"OFF"]) { return L4_OFF; } 100 | 101 | return defaultLevel; 102 | } 103 | 104 | 105 | + (L4Level *)levelWithInt:(int)aLevel 106 | { 107 | return [L4Level levelWithInt:aLevel defaultLevel:L4_DEBUG]; 108 | } 109 | 110 | + (L4Level *)levelWithInt:(int) aLevel defaultLevel:(L4Level *)defaultLevel 111 | { 112 | switch (aLevel) { 113 | case ALL_INT: return L4_ALL; 114 | case TRACE_INT:return L4_TRACE; 115 | case DEBUG_INT:return L4_DEBUG; 116 | case INFO_INT: return L4_INFO; 117 | case WARN_INT: return L4_WARN; 118 | case ERROR_INT:return L4_ERROR; 119 | case FATAL_INT:return L4_FATAL; 120 | case OFF_INT: return L4_OFF; 121 | 122 | default: 123 | return defaultLevel; 124 | } 125 | } 126 | 127 | #pragma mark - Instance methods 128 | 129 | - (id)init 130 | { 131 | return [L4Level debug]; // ok since not mutable and no "set" methods exist. 132 | } 133 | 134 | - (id)initLevel:(int)aLevel withName:(NSString *)aName syslogEquivalent:(int)sysLogLevel 135 | { 136 | self = [super init]; 137 | if (self) { 138 | _intValue = aLevel; 139 | _syslogEquivalent = sysLogLevel; 140 | _name = [aName copy]; 141 | } 142 | return self; 143 | } 144 | 145 | - (BOOL)isEqual:(id)anotherObject 146 | { 147 | if (anotherObject && [anotherObject isKindOfClass:[L4Level class]]) { 148 | L4Level *otherLevel = (L4Level *)anotherObject; 149 | return otherLevel.intValue == self.intValue && [otherLevel.stringValue isEqualToString:self.stringValue]; 150 | } 151 | return NO; 152 | } 153 | 154 | - (NSString *)description 155 | { 156 | return self.name; 157 | } 158 | 159 | - (NSString *)stringValue 160 | { 161 | return self.name; 162 | } 163 | 164 | - (BOOL)isGreaterOrEqual:(L4Level *) aLevel 165 | { 166 | return self.intValue >= aLevel.intValue; 167 | } 168 | 169 | // ### NOTE:I think this name is more apporopriate, but not changing it right now. 170 | - (BOOL)isEnabledFor:(L4Level *) aLevel 171 | { 172 | return self.intValue >= [aLevel intValue]; 173 | } 174 | 175 | @end 176 | -------------------------------------------------------------------------------- /Log4Cocoa/L4LevelMatchFilter.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "L4Filter.h" 3 | 4 | @class L4Level, L4Properties; 5 | 6 | /** 7 | * This is a very simple filter based on level matching. 8 | * 9 | * The filter admits two properties LevelToMatch and AcceptOnMatch. 10 | * 11 | * If there is an exact match between the value of the LevelToMatch option and the level of the L4LoggingEvent, then the 12 | * decide: method returns L4FilterAccept in case the AcceptOnMatch option value is set to YES/TRUE, if 13 | * it is NO/FALSE then L4FilterDeny is returned. If there is no match, L4FilterNeutral is returned. 14 | * 15 | * If levelToMatch is set to ALL, then any L4LoggingEvent is a match. 16 | */ 17 | @interface L4LevelMatchFilter : L4Filter 18 | 19 | @property (nonatomic) BOOL acceptOnMatch; /**< YES to allow logging on a match of levelToMatch, NO to prevent logging on a match. */ 20 | @property (nonatomic, weak) L4Level * levelToMatch; /**< The level to test against. */ 21 | 22 | /** 23 | * Initializes an instance from properties. The properties supported are: 24 | * - AcceptOnMatch: a string that get's converted to a BOOL. YES/NO work well. See the documentation for [NSString 25 | * boolValue] for other options. 26 | * - LevelToMatch: the name of the L4Level to match. See L4Level#levelWithName: for options. 27 | * 28 | * @param initProperties the proterties to use. 29 | * @throw L4PropertyMissingException if the LevelToMatch property is missing. 30 | */ 31 | - (id) initWithProperties:(L4Properties *)initProperties; 32 | 33 | /** 34 | * Initialze a new instance with the given values. 35 | * This is the default initializer, as the instance should have these two values set. 36 | * 37 | * @param shouldAccept YES to allow logging on a match of levelToMatch, NO to prevent logging on a match. 38 | * @param aLevel The level to test against. Not allowed to be nil. 39 | * @throw NSInvalidArgumentException if aLevel is nil. 40 | */ 41 | - (id) initWithAcceptOnMatch:(BOOL)shouldAccept andLevelToMatch:(L4Level *)aLevel; 42 | 43 | 44 | 45 | @end 46 | // For copyright & license, see LICENSE. 47 | -------------------------------------------------------------------------------- /Log4Cocoa/L4LevelMatchFilter.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | #import "L4LevelMatchFilter.h" 5 | #import "L4LogEvent.h" 6 | #import "L4Level.h" 7 | #import "L4Properties.h" 8 | 9 | @implementation L4LevelMatchFilter 10 | 11 | - (id) initWithAcceptOnMatch:(BOOL)shouldAccept andLevelToMatch:(L4Level *)aLevel 12 | { 13 | self = [super init]; 14 | if (self) { 15 | self.acceptOnMatch = shouldAccept; 16 | if (aLevel == nil) { 17 | self = nil; 18 | [NSException raise:NSInvalidArgumentException format:@"aLevel is not allowed to be nil."]; 19 | } else { 20 | self.levelToMatch = aLevel; 21 | } 22 | } 23 | return self; 24 | } 25 | 26 | - (id) initWithProperties:(L4Properties *)initProperties 27 | { 28 | self = [super initWithProperties:initProperties]; 29 | if (self) { 30 | NSString *acceptIfMatched = [initProperties stringForKey:@"AcceptOnMatch"]; 31 | self.acceptOnMatch = acceptIfMatched ? [acceptIfMatched boolValue] : YES; 32 | 33 | NSString *levelName = [initProperties stringForKey:@"LevelToMatch"]; 34 | 35 | if (levelName) { 36 | // Returns nil if no level with specified name is found 37 | self.levelToMatch = [L4Level levelWithName:levelName]; 38 | 39 | if (!self.levelToMatch) { 40 | [NSException raise:L4PropertyMissingException 41 | format:@"L4Level name [%@] not found.", levelName]; 42 | } 43 | } else { 44 | [NSException raise:L4PropertyMissingException 45 | format:@"LevelToMatch is a required property."]; 46 | } 47 | } 48 | return self; 49 | } 50 | 51 | 52 | - (L4FilterResult) decide:(L4LogEvent *)event 53 | { 54 | // Default stance. 55 | L4FilterResult action = L4FilterNeutral; 56 | if (event.level.intValue == self.levelToMatch.intValue || self.levelToMatch.intValue == [L4Level all].intValue){ 57 | action = self.acceptOnMatch ? L4FilterAccept : L4FilterDeny; 58 | } 59 | 60 | return action; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /Log4Cocoa/L4LevelRangeFilter.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "L4Filter.h" 3 | 4 | @class L4Level; 5 | 6 | /* 7 | * This is a very simple filter based on level matching, which can be used to 8 | * reject messages with priorities outside a certain range. 9 | * 10 | * The filter admits three properties LevelMin, LevelMax and AcceptOnMatch. 11 | * 12 | * If the level of the L4LoggingEvent is not between Min and Max (inclusive), 13 | * then L4FilterNeutral is returned. 14 | * 15 | * If the L4LoggingEvent level is within the specified range, then if 16 | * AcceptOnMatch is true, L4FilterAccept is returned, and if AcceptOnMatch is 17 | * false, L4FilterDeny is returned. 18 | * 19 | * If LevelMin is not defined, then there is no minimum acceptable level 20 | * (i.e. a level is never rejected for being too "low"/unimportant). 21 | * If LevelMax is not defined, then there is no maximum acceptable level 22 | * (i.e. a level is never rejected for beeing too "high"/important). 23 | */ 24 | 25 | @interface L4LevelRangeFilter : L4Filter { 26 | BOOL acceptOnMatch; /**< YES to allow logging on a match, NO to prevent logging on a match.*/ 27 | L4Level *minimumLevelToMatch; /**< the minimum L4Level to match.*/ 28 | L4Level *maximumLevelToMatch; /**< the minimum L4Level to match.*/ 29 | } 30 | 31 | /** 32 | * Initializes an instance from properties. The properties supported are: 33 | * - AcceptOnMatch: a string that get's converted to a BOOL. YES/NO work well. See the documentation for [NSString 34 | * boolValue] for other options. 35 | * - MinimumLevel: the name of the minimum L4Level to match. See L4Level#levelWithName: for options. 36 | * - MaximumLevel: the name of the maximum L4Level to match. See L4Level#levelWithName: for options. 37 | * 38 | * @param initProperties the proterties to use. 39 | */ 40 | - (id) initWithProperties:(L4Properties *)initProperties; 41 | 42 | /** 43 | * Initialze a new instance with the given values. 44 | * This is the default initializer, as the instance should have these two values set. 45 | * 46 | * @param shouldAccept YES to allow logging on a match of levelToMatch, NO to prevent logging on a match. 47 | * @param minimumLevel: the name of the minimum L4Level to match. See L4Level#levelWithName: for options. 48 | * @param maximumLevel: the name of the maximum L4Level to match. See L4Level#levelWithName: for options. 49 | */ 50 | - (id) initWithAcceptOnMatch:(BOOL)shouldAccept fromLevel:(L4Level *)minimumLevel toLevel:(L4Level *)maximumLevel; 51 | 52 | /** 53 | * Accessor for the acceptOnMatch property. 54 | */ 55 | - (BOOL) acceptOnMatch; 56 | 57 | /** 58 | * Accessor for the minimumLevelToMatch property. 59 | */ 60 | - (L4Level *) minimumLevelToMatch; 61 | 62 | /** 63 | * Accessor for the maximumLevelToMatch property. 64 | */ 65 | - (L4Level *) maximumLevelToMatch; 66 | 67 | @end 68 | // For copyright & license, see LICENSE. 69 | -------------------------------------------------------------------------------- /Log4Cocoa/L4LevelRangeFilter.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4LevelRangeFilter.h" 6 | #import "L4LogEvent.h" 7 | #import "L4Level.h" 8 | #import "L4LogLog.h" 9 | #import "L4Properties.h" 10 | 11 | @implementation L4LevelRangeFilter 12 | - (id) initWithAcceptOnMatch:(BOOL)shouldAccept fromLevel:(L4Level *)minimumLevel toLevel:(L4Level *)maximumLevel 13 | { 14 | self = [super init]; 15 | if (self != nil) { 16 | acceptOnMatch = shouldAccept; 17 | minimumLevelToMatch = minimumLevel; 18 | maximumLevelToMatch = maximumLevel; 19 | } 20 | return self; 21 | } 22 | 23 | - (id) initWithProperties:(L4Properties *) initProperties 24 | { 25 | self = [super initWithProperties:initProperties]; 26 | if(self != nil) { 27 | NSString *acceptIfMatched = [initProperties stringForKey:@"AcceptOnMatch"]; 28 | acceptOnMatch = YES; 29 | if (acceptIfMatched) { 30 | acceptOnMatch = [acceptIfMatched boolValue]; 31 | } 32 | 33 | NSString *levelMinName = [initProperties stringForKey:@"MinimumLevel"]; 34 | if (levelMinName != nil) { 35 | minimumLevelToMatch = [L4Level levelWithName:levelMinName]; 36 | } 37 | 38 | NSString *levelMaxName = [initProperties stringForKey:@"MaximumLevel"]; 39 | if (levelMaxName != nil) { 40 | maximumLevelToMatch = [L4Level levelWithName:levelMaxName]; 41 | } 42 | } 43 | return self; 44 | } 45 | 46 | 47 | - (BOOL) acceptOnMatch 48 | { 49 | return acceptOnMatch; 50 | } 51 | 52 | - (L4Level *) minimumLevelToMatch 53 | { 54 | return minimumLevelToMatch; 55 | } 56 | 57 | - (L4Level *) maximumLevelToMatch 58 | { 59 | return maximumLevelToMatch; 60 | } 61 | 62 | - (L4FilterResult) decide:(L4LogEvent *) logEvent 63 | { 64 | L4FilterResult shouldAccept = L4FilterNeutral; 65 | BOOL matches = NO; 66 | if (minimumLevelToMatch == nil || ([[logEvent level] isGreaterOrEqual:minimumLevelToMatch] == YES)) { 67 | if (maximumLevelToMatch == nil || [[logEvent level] intValue] <= [maximumLevelToMatch intValue]) { 68 | matches = YES; 69 | } else { 70 | matches = NO; 71 | } 72 | } 73 | if (matches) { 74 | shouldAccept = (acceptOnMatch == YES) ? L4FilterAccept :L4FilterDeny; 75 | } 76 | 77 | return shouldAccept; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /Log4Cocoa/L4LogEvent.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "L4LoggerProtocols.h" 3 | 4 | @class L4Level, L4Logger; 5 | 6 | /** 7 | * An event to be logged. This class embodies all of the information needed to generate a log message to an appender. 8 | */ 9 | @interface L4LogEvent : NSObject 10 | 11 | @property (readonly) L4Logger * logger; /**< The logger this event should use.*/ 12 | @property (readonly) L4Level * level; /**< The level of this event.*/ 13 | 14 | @property (readonly) NSNumber * lineNumber; /**< The line number where the event was generated.*/ 15 | @property (readonly) NSString * fileName; /**< The name of the file where the event was generated.*/ 16 | @property (readonly) NSString * methodName; /**< The name of the method where the event was generated.*/ 17 | 18 | @property (readonly) NSDate * timestamp; /**< The timestamp for when this event was generated.*/ 19 | @property (readonly) long millisSinceStart; /**< Accessor for the millisSinceStart attribute.*/ 20 | 21 | @property (readonly) NSException * exception; /**< Any exception that was logged as part of this event.*/ 22 | @property (readonly) id message; /**< The message of this event.*/ 23 | @property (readonly) NSString * renderedMessage; /**< The string version of message. */ 24 | 25 | /** 26 | * Set up the class for use. To do this we simply get the time the app was started; this value 27 | * is not exact; it is the time this class is initialized. Should be fine. 28 | */ 29 | + (void) initialize; 30 | 31 | /** 32 | * The time the class was initialized; used to determine how long an event 33 | * occured into the appliction run. 34 | * @return the start time of the application. 35 | */ 36 | + (NSDate *) startTime; 37 | 38 | /** 39 | * Creates a logging event with the given parameters. 40 | * @param aLogger the logger this event should use. 41 | * @param aLevel the level of this log event. 42 | * @param aMessage the message to be logged. 43 | * @return the new logging event. 44 | */ 45 | + (instancetype)logger:(L4Logger *) aLogger 46 | level:(L4Level *) aLevel 47 | message:(id) aMessage; 48 | 49 | /** 50 | * Creates a logging event with the given parameters. 51 | * @param aLogger the logger this event should use. 52 | * @param aLevel the level of this log event. 53 | * @param aMessage the message to be logged. 54 | * @param e an exception to go along with this log event. 55 | * @return the new logging event. 56 | */ 57 | + (instancetype)logger:(L4Logger *) aLogger 58 | level:(L4Level *) aLevel 59 | message:(id) aMessage 60 | exception:(NSException *) e; 61 | 62 | /** 63 | * Creates a logging event with the given parameters. 64 | * @param aLogger the logger this event should use. 65 | * @param aLevel the level of this log event. 66 | * @param aLineNumber the line number in the file where this event was generated. 67 | * @param aFileName the name of the file where this event was generated. 68 | * @param aMethodName the name of the method where this event was generated. 69 | * @param aMessage the message to be logged. 70 | * @param e an exception to go along with this log event. 71 | * @return the new logging event. 72 | */ 73 | + (instancetype)logger:(L4Logger *) aLogger 74 | level:(L4Level *) aLevel 75 | lineNumber:(int) aLineNumber 76 | fileName:(const char *) aFileName 77 | methodName:(const char *) aMethodName 78 | message:(id) aMessage 79 | exception:(NSException *) e; 80 | 81 | /** 82 | * Creates a logging event with the given parameters. 83 | * @param aLogger the logger this event should use. 84 | * @param aLevel the level of this log event. 85 | * @param aLineNumber the line number in the file where this event was generated. 86 | * @param aFileName the name of the file where this event was generated. 87 | * @param aMethodName the name of the method where this event was generated. 88 | * @param aMessage the message to be logged. 89 | * @param e an exception to go along with this log event. 90 | * @param aDate the time stamp for when this event was generated. 91 | * @return the new logging event. 92 | */ 93 | - (id)initWithLogger:(L4Logger *) aLogger 94 | level:(L4Level *) aLevel 95 | lineNumber:(int) aLineNumber 96 | fileName:(const char *) aFileName 97 | methodName:(const char *) aMethodName 98 | message:(id) aMessage 99 | exception:(NSException *) e 100 | eventTimestamp:(NSDate *) aDate; 101 | 102 | @end 103 | // For copyright & license, see LICENSE. 104 | -------------------------------------------------------------------------------- /Log4Cocoa/L4LogEvent.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4LogEvent.h" 6 | #import "L4Logger.h" 7 | 8 | static const int NO_LINE_NUMBER = -1; 9 | static const char *NO_FILE_NAME = ""; 10 | static const char *NO_METHOD_NAME = ""; 11 | 12 | static NSDate *startTime = nil; 13 | 14 | @implementation L4LogEvent 15 | 16 | @synthesize renderedMessage = _renderedMessage; 17 | 18 | + (void) initialize 19 | { 20 | startTime = [NSDate date]; 21 | } 22 | 23 | + (NSDate *) startTime 24 | { 25 | return startTime; 26 | } 27 | 28 | + (instancetype)logger:(L4Logger *)aLogger 29 | level:(L4Level *)aLevel 30 | message:(id)aMessage 31 | { 32 | return [self logger:aLogger 33 | level:aLevel 34 | lineNumber:NO_LINE_NUMBER 35 | fileName:NO_FILE_NAME 36 | methodName:NO_METHOD_NAME 37 | message:aMessage 38 | exception:nil]; 39 | } 40 | 41 | + (instancetype)logger:(L4Logger *)aLogger 42 | level:(L4Level *)aLevel 43 | message:(id)aMessage 44 | exception:(NSException *)e; 45 | { 46 | return [self logger:aLogger 47 | level:aLevel 48 | lineNumber:NO_LINE_NUMBER 49 | fileName:NO_FILE_NAME 50 | methodName:NO_METHOD_NAME 51 | message:aMessage 52 | exception:e]; 53 | } 54 | 55 | + (instancetype)logger:(L4Logger *)aLogger 56 | level:(L4Level *)aLevel 57 | lineNumber:(int)aLineNumber 58 | fileName:(const char *)aFileName 59 | methodName:(const char *)aMethodName 60 | message:(id)aMessage 61 | exception:(NSException *)e 62 | { 63 | return [[L4LogEvent alloc] initWithLogger:aLogger 64 | level:aLevel 65 | lineNumber:aLineNumber 66 | fileName:aFileName 67 | methodName:aMethodName 68 | message:aMessage 69 | exception:e 70 | eventTimestamp:[NSDate date]]; 71 | } 72 | 73 | - (id) init 74 | { 75 | return nil; 76 | } 77 | 78 | - (id) initWithLogger:(L4Logger *) aLogger 79 | level:(L4Level *) aLevel 80 | lineNumber:(int) aLineNumber 81 | fileName:(const char *) aFileName 82 | methodName:(const char *) aMethodName 83 | message:(id) aMessage 84 | exception:(NSException *) e 85 | eventTimestamp:(NSDate *) aDate 86 | { 87 | self = [super init]; 88 | if (self) { 89 | _lineNumber = @(aLineNumber); 90 | _fileName = @(aFileName); 91 | _methodName = @(aMethodName); 92 | 93 | _logger = aLogger; 94 | _level = aLevel; 95 | _message = aMessage; 96 | _exception = e; 97 | _timestamp = aDate; 98 | } 99 | return self; 100 | } 101 | 102 | - (long) millisSinceStart 103 | { 104 | // its a double in seconds 105 | NSTimeInterval time = [_timestamp timeIntervalSinceDate:startTime]; 106 | return (long) (time * 1000); 107 | } 108 | 109 | - (NSString *) renderedMessage 110 | { 111 | if (!_renderedMessage && _message) { 112 | if ([_message isKindOfClass:[NSString class]]) { 113 | _renderedMessage = [_message copy]; // if its a string return it. 114 | } else { 115 | _renderedMessage = [_message description]; 116 | } 117 | } 118 | 119 | return _renderedMessage; 120 | } 121 | 122 | @end 123 | -------------------------------------------------------------------------------- /Log4Cocoa/L4LogLog.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #define L4LogLog_PREFIX @"log4cocoa: " 4 | #define L4LogLog_WARN_PREFIX @"log4cocoa: WARN: " 5 | #define L4LogLog_ERROR_PREFIX @"log4cocoa: ERROR: " 6 | 7 | /** 8 | * This class is used to log when the logging system could not be properly initialized. It logs to either stdout or 9 | * stderr depending on the severity of the message. 10 | * @note This class should not be used outside the framework. 11 | */ 12 | @interface L4LogLog : NSObject 13 | 14 | /** 15 | * Is internal debugging enabled? 16 | * @return YES if it is, NO if it is not. 17 | */ 18 | + (BOOL)internalDebuggingEnabled; 19 | 20 | /** 21 | * Changes the state of internal debugging. 22 | * @param enabled YES to enable, NO to disable. 23 | */ 24 | + (void)setInternalDebuggingEnabled:(BOOL)enabled; 25 | 26 | /** 27 | * Accessor for quietModeEnabled. 28 | * @return quietModeEnabled 29 | */ 30 | + (BOOL)quietModeEnabled; 31 | 32 | /** 33 | * Mutator for quietModeEnabled. 34 | * @param enabled the new value for quietModeEnabled. 35 | */ 36 | + (void)setQuietModeEnabled:(BOOL) enabled; 37 | 38 | /** 39 | * If debuging & !quietMode, debug messages get sent to standard out, because Log4Cocoa classes 40 | * can't use Log4Cocoa loggers. 41 | * @param message the message to log. 42 | */ 43 | + (void)debug:(NSString *) message; 44 | 45 | /** 46 | * If debuging & !quietMode, debug messages get sent to standard out, because Log4Cocoa classes 47 | * can't use Log4Cocoa loggers. 48 | * @param message the message to log. 49 | * @param exception the exception to log. 50 | */ 51 | + (void)debug:(NSString *)message exception:(NSException *)exception; 52 | 53 | /** 54 | * If !quietMode, warn & error messages get sent to standard error, because Log4Cocoa classes 55 | * can't use Log4Cocoa loggers. 56 | * @param message the message to log. 57 | */ 58 | + (void)warn:(NSString *)message; 59 | 60 | /** 61 | * If !quietMode, warn & error messages get sent to standard error, because Log4Cocoa classes 62 | * can't use Log4Cocoa loggers. 63 | * @param message the message to log. 64 | * @param exception the exception to log. 65 | */ 66 | + (void)warn:(NSString *)message exception:(NSException *)exception; 67 | 68 | /** 69 | * If !quietMode, error & error messages get sent to standard error, because Log4Cocoa classes 70 | * can't use Log4Cocoa loggers. 71 | * @param message the message to log. 72 | */ 73 | + (void)error:(NSString *)message; 74 | 75 | /** 76 | * If !quietMode, error & error messages get sent to standard error, because Log4Cocoa classes 77 | * can't use Log4Cocoa loggers. 78 | * @param message the message to log. 79 | * @param exception the exception to log. 80 | */ 81 | + (void)error:(NSString *)message exception:(NSException *)exception; 82 | 83 | /** 84 | * Writes the log message to the supplied fileHandle. 85 | * @param message the message to write. 86 | * @param prefix the string to write before message. 87 | * @param fileHandle to handle to write to. 88 | * @param exception the exsception to log. 89 | */ 90 | + (void)writeMessage:(NSString *)message 91 | withPrefix:(NSString *)prefix 92 | toFile:(NSFileHandle *)fileHandle 93 | exception:(NSException *)exception; 94 | 95 | @end 96 | // For copyright & license, see LICENSE. 97 | -------------------------------------------------------------------------------- /Log4Cocoa/L4LogLog.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4LogLog.h" 6 | 7 | 8 | static BOOL internalDebugging = NO; 9 | static BOOL quietMode = NO; 10 | static NSData *lineBreakChar; 11 | 12 | @implementation L4LogLog 13 | 14 | + (void)initialize 15 | { 16 | lineBreakChar = [@"\n" dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]; 17 | } 18 | 19 | + (BOOL)internalDebuggingEnabled 20 | { 21 | return internalDebugging; 22 | } 23 | 24 | + (void)setInternalDebuggingEnabled:(BOOL)enabled 25 | { 26 | internalDebugging = enabled; 27 | } 28 | 29 | + (BOOL)quietModeEnabled 30 | { 31 | return quietMode; 32 | } 33 | 34 | + (void)setQuietModeEnabled:(BOOL) enabled 35 | { 36 | quietMode = enabled; 37 | } 38 | 39 | + (void)debug:(NSString *)message 40 | { 41 | [self debug:message exception:nil]; 42 | } 43 | 44 | + (void)debug:(NSString *)message exception:(NSException *)exception 45 | { 46 | if (internalDebugging && !quietMode) { 47 | [self writeMessage:message 48 | withPrefix:L4LogLog_PREFIX 49 | toFile:[NSFileHandle fileHandleWithStandardOutput] 50 | exception:exception]; 51 | } 52 | } 53 | 54 | + (void)warn:(NSString *)message 55 | { 56 | [self warn:message exception:nil]; 57 | } 58 | 59 | + (void)warn:(NSString *)message exception:(NSException *)exception 60 | { 61 | if (!quietMode) { 62 | [self writeMessage:message 63 | withPrefix:L4LogLog_WARN_PREFIX 64 | toFile:[NSFileHandle fileHandleWithStandardError] 65 | exception:exception]; 66 | } 67 | } 68 | 69 | + (void)error:(NSString *)message 70 | { 71 | [self error:message exception:nil]; 72 | } 73 | 74 | + (void)error:(NSString *)message exception:(NSException *)exception 75 | { 76 | if (!quietMode) { 77 | [self writeMessage:message 78 | withPrefix:L4LogLog_ERROR_PREFIX 79 | toFile:[NSFileHandle fileHandleWithStandardError] 80 | exception:exception]; 81 | } 82 | } 83 | 84 | // ### TODO *** HELP --- need a unix file expert. Should I need to flush after printf? 85 | // it doesn't work right otherwise. 86 | // 87 | // Ok, I changed and just used an NSFileHandle because its easier and it just works. 88 | // What are the performance implications, if any? 89 | // ### TODO -- must test under heavy load and talk to performance expert. 90 | // 91 | + (void)writeMessage:(NSString *)message 92 | withPrefix:(NSString *)prefix 93 | toFile:(NSFileHandle *)fileHandle 94 | exception:(NSException *)exception 95 | { 96 | @try { 97 | [fileHandle writeData: 98 | [[prefix stringByAppendingString:message] dataUsingEncoding:NSUTF8StringEncoding 99 | allowLossyConversion:YES]]; 100 | [fileHandle writeData:lineBreakChar]; 101 | 102 | if (exception) { 103 | [fileHandle writeData: 104 | [[prefix stringByAppendingString:exception.description] dataUsingEncoding:NSUTF8StringEncoding 105 | allowLossyConversion:YES]]; 106 | [fileHandle writeData:lineBreakChar]; 107 | } 108 | } 109 | @catch (NSException * exception) { 110 | // ### TODO WTF? WE'RE SCRWEDED HERE ... ABORT? EXIT? RAISE? Write Error Haiku? 111 | } 112 | } 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /Log4Cocoa/L4Logger.h: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | #import 5 | #import "L4AppenderProtocols.h" 6 | #import "L4LoggerProtocols.h" 7 | #import "L4Level.h" 8 | 9 | @class L4AppenderAttachable, L4Level, L4LogEvent; 10 | 11 | /** 12 | * This is the primary interface into the logging framework. 13 | * The functionality of the class is broken down into the following areas: 14 | *
15 | *
Base methods
16 | *
responsible for setting the level at which messages get logged.
17 | *
AppenderRelatedMethods methods
18 | *
responsible for adding, calling, and removing L4Appender instances.
19 | *
CoreLoggingMethods methods
20 | *
the methods that do the actual logging.
21 | *
LogManagerCoverMethods methods
22 | *
Class methods the handle the chain of L4Logger instances, and find the correct L4Logger 23 | * instance for a given class or logger name.
24 | *
25 | */ 26 | @interface L4Logger : NSObject { 27 | L4AppenderAttachable *aai; /**< What does the actual appending for this logger.*/ 28 | } 29 | 30 | /** 31 | * The level of this logger. Nil means use parent's 32 | */ 33 | @property (strong) L4Level * level; 34 | 35 | /** 36 | * The name of this logger. 37 | */ 38 | @property (readonly) NSString * name; 39 | 40 | /** 41 | * The parent of this logger. 42 | */ 43 | @property (strong) L4Logger * parent; 44 | 45 | /** 46 | * Flag for if log messages are additive. If YES, logging events are set to parent loggers. 47 | * If NO, parents are not called. 48 | */ 49 | @property BOOL additivity; 50 | 51 | /** 52 | * Acccessor for this loggers repository. 53 | */ 54 | @property (strong) id repository; 55 | 56 | 57 | /** 58 | * DON'T USE, only for use of log manager 59 | * @param loggerName the name of this logger. 60 | */ 61 | - (id)initWithName:(NSString *)loggerName; 62 | 63 | /** 64 | * The efective level for this logger. Events with a level below this will not be logged. 65 | * @return the minimum level this logger will log. 66 | */ 67 | - (L4Level *)effectiveLevel; 68 | 69 | /* ********************************************************************* */ 70 | #pragma mark AppenderRelatedMethods methods 71 | /* ********************************************************************* */ 72 | /** 73 | * Appends the logging event to every appender attached to this logger. If this logger is additive, the 74 | * message propagates up the parent chain until all of them have been called, or one is found that is 75 | * not addative. 76 | * If no appender could be found to append to, a warning is emited to that effect. 77 | * @param event the logging event. 78 | */ 79 | - (void) callAppenders:(L4LogEvent *) event; 80 | 81 | /** 82 | * Accessor for the appender attached to this logger. 83 | */ 84 | - (L4AppenderAttachable *) aai; 85 | 86 | /** 87 | * Accessor for the appenders attached to this logger. A L4AppenderAttachableImpl can have more than one 88 | * logger attached to it. 89 | */ 90 | - (NSArray *) allAppenders; 91 | 92 | /** 93 | * Accessor for named appender if in list. 94 | * @param aName the name of the appender to find. 95 | * @return if found the appender. Otherwise, nil. 96 | */ 97 | - (id ) appenderWithName:(NSString *) aName; 98 | 99 | /** 100 | * Adds an appender to those attached to this logger instance. 101 | * @param appender the L4Appender to add. If nil, a new L4AppenderAttachableImpl is created and added. 102 | */ 103 | - (void) addAppender:(id ) appender; 104 | /** 105 | * Determines if a given L4Appender is attached to this logger instance. 106 | * @param appender the L4Appender of interest. 107 | * @return YES if it is attached, NO if it is not. 108 | */ 109 | - (BOOL) isAttached:(id ) appender; 110 | 111 | /** 112 | * Closes all appenders attached to this logging instance. 113 | */ 114 | - (void) closeNestedAppenders; 115 | 116 | /** 117 | * Removes all appenders attached to this logging instance. 118 | */ 119 | - (void) removeAllAppenders; 120 | 121 | /** 122 | * Removes the given appender from those attached to this logging instance. 123 | * @param appender to be removed. 124 | */ 125 | - (void) removeAppender:(id ) appender; 126 | 127 | /** 128 | * Removes the appender with the given name from those attached to this logging instance. 129 | * @param aName the name of the appender to be removed. 130 | */ 131 | - (void) removeAppenderWithName:(NSString *) aName; 132 | 133 | /* ********************************************************************* */ 134 | #pragma mark CoreLoggingMethods methods 135 | /* ********************************************************************* */ 136 | /* ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF */ 137 | 138 | /** 139 | * Determines if a trace message should be logged. 140 | * @return YES if debug messages are enabled, NO if they are not. 141 | */ 142 | - (BOOL)isTraceEnabled; 143 | 144 | /** 145 | * Determines if a debug message should be logged. 146 | * @return YES if debug messages are enabled, NO if they are not. 147 | */ 148 | - (BOOL)isDebugEnabled; 149 | 150 | /** 151 | * Determines if an info message should be logged. 152 | * @return YES if info messages are enabled, NO if they are not. 153 | */ 154 | - (BOOL)isInfoEnabled; 155 | 156 | /** 157 | * Determines if a warn message should be logged. 158 | * @return YES if warn messages are enabled, NO if they are not. 159 | */ 160 | - (BOOL)isWarnEnabled; 161 | 162 | /** 163 | * Determines if an error message should be logged. 164 | * @return YES if error messages are enabled, NO if they are not. 165 | */ 166 | - (BOOL)isErrorEnabled; 167 | 168 | /** 169 | * Determines if a fatel message should be logged. 170 | * @return YES if fatel messages are enabled, NO if they are not. 171 | */ 172 | - (BOOL)isFatalEnabled; 173 | 174 | /** 175 | * Determines if aLevel should be logged. 176 | * @param aLevel the L4Level to be checked. 177 | * @return YES if logging is enabled for the level, NO if it is not. 178 | */ 179 | - (BOOL)isEnabledFor:(L4Level *) aLevel; 180 | 181 | /** 182 | * Logs an error message in an NSAssert is false. 183 | * This is considered a framework method, and probably should not be called from outside the framework. 184 | * 185 | * @param lineNumber the line number in the source file where the assertion is. 186 | * @param fileName the name of the source file containing the assertion. 187 | * @param methodName the name of the method containing the assertion. 188 | * @param anAssertion the NSAssert to be tested. 189 | * @param aMessage the message to be logged if the assertian is false. 190 | */ 191 | - (void)lineNumber:(int) lineNumber 192 | fileName:(char *) fileName 193 | methodName:(char *) methodName 194 | assert:(BOOL) anAssertion 195 | log:(NSString *) aMessage; 196 | 197 | 198 | /** 199 | * Logs a message with an excpetion at a level of fatal. 200 | * This is considered a framework method, and probably should not be called from outside the framework. 201 | * 202 | * @param lineNumber the line number in the source file where the log statement is. 203 | * @param fileName the name of the source file containing the log statement. 204 | * @param methodName the name of the method containing the log statement. 205 | * @param aMessage the message to be logged. 206 | * @param aLevel the L4Level for this log message. 207 | * @param e the exception to be logged. 208 | */ 209 | - (void)lineNumber:(int) lineNumber 210 | fileName:(char *) fileName 211 | methodName:(char *) methodName 212 | message:(id) aMessage 213 | level:(L4Level *) aLevel 214 | exception:(NSException *) e; 215 | 216 | /* This is the designated logging method that the others invoke. */ 217 | /** 218 | * Forwards the L4LoggingEvent to all attached appenders; the other methods in this class create 219 | * an L4LoggingEvent and call this method. 220 | * This is considered a framework method, and probably should not be called from outside the framework. 221 | * 222 | * @param event the event to be logged. 223 | */ 224 | - (void) forcedLog:(L4LogEvent *) event; 225 | 226 | 227 | #pragma mark - Logger management methods 228 | 229 | /** 230 | * Accessor for the logger repository. 231 | * @return the logger repository. 232 | */ 233 | + (id )loggerRepository; 234 | 235 | /** 236 | * Accessor for the root logger. 237 | * @return the root logger. 238 | */ 239 | + (instancetype)rootLogger; 240 | 241 | /** 242 | * Accesses the logger for the given class. 243 | * @param aClass the class we want the logger for. 244 | * @return the logger for the class 245 | */ 246 | + (instancetype)loggerForClass:(Class) aClass; 247 | 248 | /** 249 | * Accesses the logger for the given name. 250 | * @param aName the name of the logger we want. 251 | * @return the logger for the class 252 | */ 253 | + (instancetype)loggerForName:(NSString *) aName; 254 | 255 | /** 256 | * Accesses the logger for the given name. 257 | * @param aName the name of the logger we want. 258 | * @param aFactory the factory to use to create the logger if it does not yet exist. 259 | * @return the logger for the class 260 | */ 261 | + (instancetype)loggerForName:(NSString *) aName factory:(id ) aFactory; 262 | 263 | /** 264 | * The array of loggers. 265 | * @return the current loggers. 266 | */ 267 | + (NSArray *)currentLoggers; 268 | 269 | /** 270 | * Shut down logging. 271 | */ 272 | + (void)shutdown; 273 | 274 | /** 275 | * Reset the logging configuration. 276 | */ 277 | + (void)resetConfiguration; 278 | 279 | @end 280 | 281 | /** 282 | * This class is a dummy class; its only purpose is to facilitate logging from 283 | * methods. It serves as the 'self' argument. Log level for methods can be 284 | * adjusted with this, but keep in mind that it applies to all function logging. 285 | */ 286 | @interface L4FunctionLogger :NSObject 287 | { 288 | L4FunctionLogger *instance; /**< the singleon instance of this class. */ 289 | } 290 | /** 291 | * Accessor for the singleton instance. 292 | */ 293 | + (L4FunctionLogger *)instance; 294 | @end 295 | -------------------------------------------------------------------------------- /Log4Cocoa/L4Logger.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4Logger.h" 6 | #import "L4AppenderAttachable.h" 7 | #import "L4Level.h" 8 | #import "L4LoggerStore.h" 9 | #import "L4LogEvent.h" 10 | #import "L4LogLog.h" 11 | #import "L4RootLogger.h" 12 | 13 | 14 | static L4LoggerStore *_loggerRepository = nil; 15 | 16 | @implementation L4Logger 17 | 18 | + (void) initialize 19 | { 20 | id rootLogger = [[L4RootLogger alloc] initWithLevel:[L4Level debug]]; 21 | _loggerRepository = [[L4LoggerStore alloc] initWithRoot:rootLogger]; 22 | 23 | // Initialize Event timmer 24 | [L4LogEvent startTime]; 25 | } 26 | 27 | - (id)init 28 | { 29 | return nil; // never use this constructor 30 | } 31 | 32 | - (id)initWithName:(NSString *) aName 33 | { 34 | self = [super init]; 35 | 36 | if (self) { 37 | _name = [aName copy]; 38 | _additivity = YES; 39 | } 40 | 41 | return self; 42 | } 43 | 44 | - (L4Level *) effectiveLevel 45 | { 46 | @synchronized(self) { 47 | for (L4Logger *logger = self; logger; logger = logger.parent) { 48 | if (logger.level) 49 | return logger.level; 50 | } 51 | } 52 | 53 | [L4LogLog error:@"Root Logger Not Found!"]; 54 | return nil; 55 | } 56 | 57 | 58 | #pragma mark - AppenderRelatedMethods methods 59 | 60 | - (void)callAppenders:(L4LogEvent *) event 61 | { 62 | int writes = 0; 63 | 64 | @synchronized(self) { 65 | for (L4Logger *logger = self; logger; logger = logger.parent) { 66 | if (logger.aai) 67 | writes += [logger.aai appendLoopOnAppenders:event]; 68 | 69 | if (!logger.additivity) 70 | break; 71 | } 72 | } 73 | 74 | if (writes == 0) 75 | [self.repository emitNoAppenderWarning:self]; 76 | } 77 | 78 | - (L4AppenderAttachable *) aai 79 | { 80 | return aai; 81 | } 82 | 83 | - (NSArray *) allAppenders 84 | { 85 | return [aai allAppenders]; 86 | } 87 | 88 | - (id ) appenderWithName:(NSString *) aName 89 | { 90 | return [aai appenderWithName:aName]; 91 | } 92 | 93 | - (void) addAppender:(id ) appender 94 | { 95 | @synchronized(self) { 96 | if (!aai) 97 | aai = [[L4AppenderAttachable alloc] init]; 98 | 99 | [aai addAppender:appender]; 100 | } 101 | } 102 | 103 | - (BOOL) isAttached:(id ) appender 104 | { 105 | BOOL isAttached = NO; 106 | @synchronized(self) { 107 | if((appender != nil) && (aai != nil)) { 108 | isAttached = [aai isAttached:appender]; 109 | } 110 | } 111 | return isAttached; 112 | } 113 | 114 | - (void) closeNestedAppenders 115 | { 116 | @synchronized(self) { 117 | NSEnumerator *enumerator = [[self allAppenders] objectEnumerator]; 118 | id anObject; 119 | 120 | while ((anObject = (id )[enumerator nextObject])) { 121 | [anObject close]; 122 | } 123 | } 124 | } 125 | 126 | - (void) removeAllAppenders 127 | { 128 | @synchronized(self) { 129 | [aai removeAllAppenders]; 130 | aai = nil; 131 | } 132 | } 133 | 134 | - (void) removeAppender:(id ) appender 135 | { 136 | [aai removeAppender:appender]; 137 | } 138 | 139 | - (void) removeAppenderWithName:(NSString *) aName 140 | { 141 | [aai removeAppenderWithName:aName]; 142 | } 143 | 144 | 145 | #pragma mark - CoreLoggingMethods methods 146 | 147 | // ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF 148 | 149 | - (BOOL) isTraceEnabled 150 | { 151 | return [self isEnabledFor:[L4Level trace]]; 152 | } 153 | - (BOOL) isDebugEnabled 154 | { 155 | return [self isEnabledFor:[L4Level debug]]; 156 | } 157 | - (BOOL) isInfoEnabled 158 | { 159 | return [self isEnabledFor:[L4Level info]]; 160 | } 161 | - (BOOL) isWarnEnabled 162 | { 163 | return [self isEnabledFor:[L4Level warn]]; 164 | } 165 | - (BOOL) isErrorEnabled 166 | { 167 | return [self isEnabledFor:[L4Level error]]; 168 | } 169 | - (BOOL) isFatalEnabled 170 | { 171 | return [self isEnabledFor:[L4Level fatal]]; 172 | } 173 | 174 | - (BOOL) isEnabledFor:(L4Level *) aLevel 175 | { 176 | if([self.repository isDisabled:[aLevel intValue]]) { 177 | return NO; 178 | } 179 | return [aLevel isGreaterOrEqual:[self effectiveLevel]]; 180 | } 181 | 182 | - (void) lineNumber:(int) lineNumber 183 | fileName:(char *) fileName 184 | methodName:(char *) methodName 185 | assert:(BOOL) anAssertion 186 | log:(NSString *) aMessage 187 | { 188 | if( !anAssertion ) { 189 | [self lineNumber:lineNumber 190 | fileName:fileName 191 | methodName:methodName 192 | message:aMessage 193 | level:[L4Level error] 194 | exception:nil]; 195 | } 196 | } 197 | 198 | - (void) lineNumber:(int) lineNumber 199 | fileName:(char *) fileName 200 | methodName:(char *) methodName 201 | message:(id) aMessage 202 | level:(L4Level *) aLevel 203 | exception:(NSException *) e 204 | { 205 | if ([self.repository isDisabled:[aLevel intValue]]) { 206 | return; 207 | } 208 | 209 | if([aLevel isGreaterOrEqual:[self effectiveLevel]]) { 210 | [self forcedLog:[L4LogEvent logger:self 211 | level:aLevel 212 | lineNumber:lineNumber 213 | fileName:fileName 214 | methodName:methodName 215 | message:aMessage 216 | exception:e]]; 217 | } 218 | } 219 | 220 | - (void) forcedLog:(L4LogEvent *) event 221 | { 222 | [self callAppenders:event]; 223 | } 224 | 225 | 226 | #pragma mark - Logger management methods 227 | 228 | + (id ) loggerRepository 229 | { 230 | return _loggerRepository; 231 | } 232 | 233 | + (instancetype)rootLogger 234 | { 235 | return [_loggerRepository rootLogger]; 236 | } 237 | 238 | + (instancetype)loggerForClass:(Class) aClass 239 | { 240 | return [_loggerRepository loggerForClass:aClass]; 241 | } 242 | 243 | + (instancetype)loggerForName:(NSString *) aName 244 | { 245 | return [_loggerRepository loggerForName:aName]; 246 | } 247 | 248 | + (instancetype)loggerForName:(NSString *) aName factory:(id ) aFactory 249 | { 250 | return [_loggerRepository loggerForName:aName factory:aFactory]; 251 | } 252 | 253 | + (NSArray *) currentLoggers 254 | { 255 | return [_loggerRepository currentLoggers]; 256 | } 257 | 258 | + (void) shutdown 259 | { 260 | return [_loggerRepository shutdown]; 261 | } 262 | 263 | + (void) resetConfiguration 264 | { 265 | return [_loggerRepository resetConfiguration]; 266 | } 267 | 268 | @end 269 | 270 | 271 | @implementation L4FunctionLogger 272 | static L4FunctionLogger *instance; 273 | + (L4FunctionLogger *)instance 274 | { 275 | if (instance == nil) { 276 | instance = [[L4FunctionLogger alloc] init]; 277 | } 278 | return instance; 279 | } 280 | 281 | @end 282 | -------------------------------------------------------------------------------- /Log4Cocoa/L4LoggerNameMatchFilter.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "L4Filter.h" 3 | 4 | 5 | /* 6 | * This is a very simple filter based on string matching of the logger name. 7 | * 8 | * The filter admits two options StringToMatch and AcceptOnMatch. 9 | * 10 | * If there is a match between the value of the StringToMatch option and the logger name of the L4LoggingEvent, then the 11 | * decide(L4LoggingEvent) method returns L4FilterAccept if the AcceptOnMatch option value is YES/TRUE, if it is 12 | * NO/FALSE, then L4FilterDeny is returned. If there is no match, L4FilterNeutral is returned. 13 | */ 14 | @interface L4LoggerNameMatchFilter : L4Filter { 15 | BOOL acceptOnMatch; /**< YES to allow logging on a match, NO to prevent logging on a match. */ 16 | NSString *stringToMatch; /**< The string that the logging events logger name must contain to match this filter. */ 17 | } 18 | 19 | /** 20 | * Initializes an instance from properties. The properties supported are: 21 | * - AcceptOnMatch: a string that get's converted to a BOOL. YES/NO work well. See the documentation for [NSString 22 | * boolValue] for other options. 23 | * - StringToMatch: the string that the logging events logger name must contain to match this filter. 24 | * 25 | * @param initProperties the proterties to use. 26 | * @throw L4PropertyMissingException if the StringToMatch property is missing. 27 | */ 28 | - (id) initWithProperties:(L4Properties *)initProperties; 29 | 30 | /** 31 | * Initialze a new instance with the given values. 32 | * This is the default initializer, as the instance should have these two values set. 33 | * 34 | * @param shouldAccept YES to allow logging on a match of levelToMatch, NO to prevent logging on a match. 35 | * @param aString the string that the logging events logger name must contain to match this filter. 36 | * @throw NSInvalidArgumentException if aString is nil. 37 | */ 38 | - (id) initWithAcceptOnMatch:(BOOL)shouldAccept stringToMatch:(NSString *)aString; 39 | 40 | 41 | /** 42 | * Accessor for the acceptOnMatch property. 43 | */ 44 | - (BOOL) acceptOnMatch; 45 | 46 | /** 47 | * Accessor for the stringToMatch property. 48 | */ 49 | - (NSString *) stringToMatch; 50 | 51 | @end 52 | // For copyright & license, see LICENSE. 53 | -------------------------------------------------------------------------------- /Log4Cocoa/L4LoggerNameMatchFilter.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4LoggerNameMatchFilter.h" 6 | #import "L4LogEvent.h" 7 | #import "L4Properties.h" 8 | #import "L4Logger.h" 9 | 10 | @implementation L4LoggerNameMatchFilter 11 | 12 | - (id) initWithAcceptOnMatch:(BOOL)shouldAccept stringToMatch:(NSString *)aString 13 | { 14 | self = [super init]; 15 | if (self) { 16 | acceptOnMatch = shouldAccept; 17 | if (aString == nil || [aString length] == 0) { 18 | self = nil; 19 | [NSException raise:NSInvalidArgumentException format:@"aString is not allowed to be nil."]; 20 | } else { 21 | stringToMatch = aString; 22 | } 23 | } 24 | return self; 25 | } 26 | 27 | -(id) initWithProperties: (L4Properties *) initProperties 28 | { 29 | self = [super initWithProperties: initProperties]; 30 | if (self != nil) { 31 | NSString *acceptIfMatched = [initProperties stringForKey:@"AcceptOnMatch"]; 32 | acceptOnMatch = YES; 33 | 34 | if (acceptIfMatched) { 35 | acceptOnMatch = [acceptIfMatched boolValue]; 36 | } 37 | 38 | stringToMatch = [initProperties stringForKey:@"StringToMatch"]; 39 | if (stringToMatch == nil) { 40 | [NSException raise:L4PropertyMissingException format: @"StringToMatch is a required property."]; 41 | } 42 | } 43 | return self; 44 | } 45 | 46 | 47 | -(BOOL) acceptOnMatch 48 | { 49 | return acceptOnMatch; 50 | } 51 | 52 | -(NSString *) stringToMatch 53 | { 54 | return stringToMatch; 55 | } 56 | 57 | -(L4FilterResult) decide:(L4LogEvent *) logEvent 58 | { 59 | L4FilterResult filterResult = L4FilterNeutral; 60 | if ([[logEvent logger] name] != nil && [[[logEvent logger] name] rangeOfString:stringToMatch].location != NSNotFound) { 61 | filterResult = (acceptOnMatch ? L4FilterAccept : L4FilterDeny); 62 | } 63 | 64 | return filterResult; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /Log4Cocoa/L4LoggerProtocols.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class L4Logger, L4Level, L4RendererMap; 4 | 5 | /** 6 | * A factory to be used to instantiate L4Logging classes. 7 | */ 8 | @protocol L4LoggerFactory 9 | 10 | /** 11 | * Creates a new logger with the given name. 12 | * @param aName the name to use for the new logger. 13 | * @return the newly created logger. 14 | */ 15 | - (L4Logger *)newLoggerInstance:(NSString *)aName; 16 | 17 | @end 18 | 19 | /** 20 | * A repository for logger instances in the framework. 21 | */ 22 | @protocol L4LoggerRepository 23 | 24 | @property L4Level *threshold; /**< The minimum level to log.*/ 25 | 26 | /** 27 | * Is the repository disabled for a given level? The answer depends on the repository threshold and the 28 | * level parameter. 29 | */ 30 | - (BOOL) isDisabled:(int) aLevel; 31 | 32 | /** 33 | * Mutator for the threshold property. 34 | * @param aLevelName the name of a L4LEvel to use as the new threshold level. 35 | */ 36 | - (void) setThresholdByName:(NSString *)aLevelName; 37 | 38 | /** 39 | * Accessor for the root logger. 40 | * @returns the root logger for the system. 41 | */ 42 | - (L4Logger *)rootLogger; 43 | 44 | /** 45 | * Returns the logger for a particular class. 46 | * @param aClass the class who's logger is being requested. 47 | * @return the logger for the given class, creating it if need be. 48 | */ 49 | - (L4Logger *)loggerForClass:(Class) aClass; 50 | 51 | /** 52 | * Returns the logger for a particular class. 53 | * @param aName the classes name for the logger being requested. 54 | * @return the logger for the given class, creating it if need be. 55 | */ 56 | - (L4Logger *)loggerForName:(NSString *)aName; 57 | 58 | /** 59 | * Returns the logger for a particular class. 60 | * @param aName the classes name for the logger being requested. 61 | * @param aFactory the factory to use to create the logger if it does not already exist. 62 | * @return the logger for the given class, creating it if need be. 63 | */ 64 | - (L4Logger *)loggerForName:(NSString *)aName factory:(id ) aFactory; 65 | 66 | /** 67 | * Returns the collection of all loggers. 68 | * @return the loggers configured in the system. 69 | */ 70 | - (NSArray *)currentLoggers; 71 | 72 | /** 73 | * Tells the logging system not to emi warning messages when an appender does not exist. 74 | * @param aLogger the logger to ignore. 75 | */ 76 | - (void) emitNoAppenderWarning:(L4Logger *)aLogger; 77 | 78 | /** 79 | * Resets the configuration of the logging system. 80 | */ 81 | - (void) resetConfiguration; 82 | 83 | /** 84 | * Shuts down the logging system. 85 | */ 86 | - (void) shutdown; 87 | 88 | @end 89 | 90 | // For copyright & license, see LICENSE. 91 | -------------------------------------------------------------------------------- /Log4Cocoa/L4LoggerStore.h: -------------------------------------------------------------------------------- 1 | // For copyright & license, see LICENSE. 2 | #import 3 | #import "L4LoggerProtocols.h" 4 | 5 | @class L4Level, L4Logger; 6 | 7 | /** 8 | * Manages all logger instances. 9 | * TODO Add support the L4RendererSupport protocol, called from the L4LoggingEvent:renderedMessage method. 10 | */ 11 | @interface L4LoggerStore : NSObject 12 | 13 | @property (strong) L4Level *threshold; /**< The minimum level to log.*/ 14 | 15 | /** 16 | * the following are L4LoggerRepository methods 17 | * @param rootLogger the root logger. 18 | */ 19 | - (id)initWithRoot:(id) rootLogger; 20 | 21 | /** 22 | * Is the repository disabled for a given level? The answer depends 23 | * on the repository threshold and the parameter. 24 | * See also setThreshold: method. 25 | * @param aLevel the level to check. 26 | * @return YES if logging is disabled for the specified level; NO if it is not. 27 | */ 28 | - (BOOL)isDisabled:(int) aLevel; 29 | 30 | /** 31 | * Sets the threshold to the level with a name that matches the parameter. 32 | * @param aLevelName the name of the level to set the threshold to. 33 | */ 34 | - (void)setThresholdByName:(NSString *)aLevelName; 35 | 36 | /** 37 | * Accessor for the root logger. 38 | * @return the root logger. 39 | */ 40 | - (L4Logger *)rootLogger; 41 | 42 | /** 43 | * Gets a logger for the class object, if it doesn't exist already 44 | * it is created by composing the pseudo-fqcn and then calling 45 | * loggerForName:factory: which does the hard work. 46 | */ 47 | - (L4Logger *)loggerForClass:(Class) aClass; 48 | 49 | /** a wrapper for loggerForName:factory: with self as the factory */ 50 | - (L4Logger *)loggerForName:(NSString *) aName; 51 | 52 | /** 53 | * returns a logger with name or creates it & inserts it into the 54 | * repository and hooks up all pointers to pre-existing parents 55 | * children efficiently (thanks to the Log4J folks algorithm). 56 | */ 57 | - (L4Logger *)loggerForName:(NSString *) aName factory:(id ) aFactory; 58 | 59 | /** 60 | * Accessor for the collection of loggers. 61 | * @return the array of loggers. 62 | */ 63 | - (NSArray *)currentLoggers; 64 | 65 | /** 66 | * Warn that aLogger has no appenders configured; only if no warning has already 67 | * been given. 68 | * @param aLogger the logger to warn about. 69 | */ 70 | - (void)emitNoAppenderWarning:(L4Logger *) aLogger; 71 | 72 | /** 73 | * Resets the configuration of the log store. 74 | */ 75 | - (void)resetConfiguration; 76 | /** 77 | Shut down logging. 78 | */ 79 | - (void)shutdown; 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /Log4Cocoa/L4LoggerStore.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4LoggerStore.h" 6 | //#import 7 | #import "L4Logger.h" 8 | #import "L4Level.h" 9 | #import "L4LogLog.h" 10 | 11 | /** 12 | * Private methods. 13 | */ 14 | @interface L4LoggerStore () 15 | /** 16 | * Generates a psuedo-fully qualified class name for the class using java-esque dot "." notation seperating classes from 17 | * their parents. For example L4Logger results in the string - NSObject.L4Logger 18 | * NSObject.SubClass1.SubClass2.LeafClass is the format 19 | * @param aClass the class of interest. 20 | * @return the FQDN for the class of interest. 21 | */ 22 | - (NSString *) pseudoFqcnForClass:(Class) aClass; 23 | /** 24 | * Update parents, starts at the end of the pseudo-fqcn string and looks for the first matching logger and sets that as 25 | * the parent. Each element in the pseudo-fqcn key path that doesn't exist is created as an NSMutableArray. 26 | * In order to collect all the child loggers underneath that point in the hierarchy. This is important when a node in 27 | * the middle of the tree is created. All children of that node are set to have that new logger as their parent. 28 | * @param aLogger the logger who's parents we want to update. 29 | */ 30 | - (void) updateParentsOfLogger:(L4Logger *) aLogger; 31 | 32 | /** 33 | * If the child already has a parent lower than this new logger leave it alone. If the parent is above this new logger, 34 | * then insert itself inbetween. 35 | * @param node the children to update. 36 | * @param parent the new parent of the children. 37 | */ 38 | - (void) updateChildren:(NSMutableArray *) node withParent:(L4Logger *) parent; 39 | @end 40 | 41 | 42 | @implementation L4LoggerStore { 43 | L4Logger * _root; /**< The root logger.*/ 44 | NSMutableDictionary * _repository; /**< The collection of loggers by id.*/ 45 | NSMutableArray * _loggers; /**< The collection of loggers.*/ 46 | int _thresholdInt; /**< The minimum level as an int to log.*/ 47 | 48 | BOOL _emittedNoAppenderWarning; /**< Tracks if a warning should be created if there are no appenders configured.*/ 49 | BOOL _emittedNoResourceBundleWarning;/**< Tracks if a warning should be created if there is no resource bundle.*/ 50 | } 51 | 52 | - (id) init 53 | { 54 | return nil; // don't use this method 55 | } 56 | 57 | - (id) initWithRoot:(id) rootLogger 58 | { 59 | self = [super init]; 60 | if (self) { 61 | _root = rootLogger; 62 | 63 | _repository = [NSMutableDictionary dictionary]; 64 | _loggers = [NSMutableArray arrayWithObject:_root]; 65 | 66 | _root.repository = self; 67 | self.threshold = [L4Level all]; 68 | 69 | _emittedNoAppenderWarning = NO; 70 | _emittedNoResourceBundleWarning = NO; 71 | } 72 | return self; 73 | } 74 | 75 | 76 | - (BOOL) isDisabled:(int) aLevel 77 | { 78 | return _thresholdInt > aLevel; 79 | } 80 | 81 | - (void) setThresholdByName:(NSString *) aLevelName 82 | { 83 | self.threshold = [L4Level levelWithName:aLevelName defaultLevel:self.threshold]; 84 | } 85 | 86 | - (L4Logger *) rootLogger 87 | { 88 | return _root; 89 | } 90 | 91 | - (L4Logger *) loggerForClass:(Class) aClass 92 | { 93 | if (!aClass) 94 | return nil; 95 | 96 | id logger; 97 | @synchronized(self) { 98 | logger = _repository[NSStringFromClass(aClass)]; 99 | 100 | if (!logger) { 101 | NSString *pseudoFqcn = [self pseudoFqcnForClass:aClass]; 102 | logger = [self loggerForName:pseudoFqcn factory:self]; 103 | _repository[NSStringFromClass(aClass)] = logger; 104 | } 105 | } 106 | 107 | return logger; 108 | } 109 | 110 | - (L4Logger *) loggerForName:(NSString *) aName 111 | { 112 | return [self loggerForName:aName factory:self]; 113 | } 114 | 115 | - (L4Logger *) loggerForName:(NSString *) aName factory:(id ) aFactory 116 | { 117 | L4Logger *theLogger = nil; 118 | id theNode; 119 | 120 | @synchronized(self) { 121 | theNode = _repository[aName]; 122 | 123 | if( theNode == nil ) { 124 | // 125 | // if the node is nil, then its a new logger & therefore 126 | // a new leaf node, since no placeholder node was found. 127 | // 128 | theLogger = [aFactory newLoggerInstance:aName]; 129 | theLogger.repository = self; 130 | _repository[aName] = theLogger; 131 | [self updateParentsOfLogger:theLogger]; 132 | [_loggers addObject:theLogger]; 133 | 134 | } else if([theNode isKindOfClass:[L4Logger class]]) { 135 | theLogger = (L4Logger *) theNode; 136 | } else if([theNode isKindOfClass:[NSMutableArray class]]) { 137 | // 138 | // this node is a placeholder middle node, since its an NSMutableArray. It contains children and 139 | // a parent, so when we insert this logger, we need to update all of the children to point to this node 140 | // and to point to their parent. 141 | // 142 | theLogger = [aFactory newLoggerInstance:aName]; 143 | theLogger.repository = self; 144 | _repository[aName] = theLogger; 145 | [self updateChildren:theNode withParent:theLogger ]; 146 | [self updateParentsOfLogger:theLogger]; 147 | [_loggers addObject:theLogger]; 148 | 149 | } else { 150 | // We should hopefully never end up here. ### TODO ??? - Internal Consistency Error 151 | // 152 | NSString *one = @"Logger not found & internal repository in returned unexpected node type:"; 153 | NSString *twoDo = @" ### TODO:Should we raise here, because we shouldn't be here."; 154 | [L4LogLog error: 155 | [[one stringByAppendingString: 156 | NSStringFromClass([theNode class])] stringByAppendingString:twoDo]]; 157 | } 158 | } 159 | 160 | return (L4Logger *) theLogger; 161 | } 162 | 163 | - (NSArray *)currentLoggers 164 | { 165 | @synchronized(self) { 166 | return [_loggers copy]; 167 | } 168 | } 169 | 170 | - (void) emitNoAppenderWarning:(L4Logger *) aLogger 171 | { 172 | if (!_emittedNoAppenderWarning ) { 173 | [L4LogLog warn:[NSString stringWithFormat:@"No appenders could be found for logger(%@).", [aLogger name]]]; 174 | [L4LogLog warn:@"Please initialize the Log4Cocoa system properly."]; 175 | 176 | _emittedNoAppenderWarning = YES; 177 | } 178 | } 179 | 180 | - (void) resetConfiguration 181 | { 182 | @synchronized(self) { 183 | _root.level = [L4Level debug]; 184 | self.threshold = [L4Level all]; 185 | 186 | [self shutdown]; 187 | 188 | for (L4Logger *logger in _loggers) { 189 | logger.level = nil; 190 | logger.additivity = YES; 191 | } 192 | } 193 | } 194 | 195 | - (void) shutdown 196 | { 197 | @synchronized(self) { 198 | [_root closeNestedAppenders]; 199 | 200 | for (L4Logger *logger in _loggers) 201 | [logger closeNestedAppenders]; 202 | 203 | [_root removeAllAppenders]; 204 | 205 | for (L4Logger *logger in _loggers) 206 | [logger removeAllAppenders]; 207 | } 208 | } 209 | 210 | 211 | #pragma mark - L4LoggerFactoryCategory methods 212 | 213 | - (L4Logger *) newLoggerInstance:(NSString *) aName 214 | { 215 | return [[L4Logger alloc] initWithName:aName]; 216 | } 217 | 218 | 219 | #pragma mark - Private methods 220 | 221 | - (NSString *) pseudoFqcnForClass:(Class) aClass 222 | { 223 | NSMutableString *pseudoFqcn = [[NSMutableString alloc] init]; 224 | Class theClass = aClass; 225 | 226 | [pseudoFqcn insertString:NSStringFromClass(theClass) atIndex:0]; 227 | 228 | for( theClass = [theClass superclass]; theClass != nil; theClass = [theClass superclass] ) { 229 | [pseudoFqcn insertString:@"." atIndex:0]; 230 | [pseudoFqcn insertString:NSStringFromClass(theClass) atIndex:0]; 231 | } 232 | return pseudoFqcn; 233 | } 234 | 235 | - (void) updateParentsOfLogger:(L4Logger *) aLogger 236 | { 237 | L4Logger *parent = _root; 238 | 239 | // the trick here is to build up the key paths in reverse order not including 240 | // the pseudo-fqcn (i.e the last node), to search for already existing parents 241 | NSArray *keys = [aLogger.name componentsSeparatedByString:@"."]; 242 | for (int i = keys.count - 1; i > 0; --i) { 243 | NSString *keyPath = [[keys subarrayWithRange:NSMakeRange(0, i)] componentsJoinedByString:@"."]; 244 | id node = _repository[keyPath]; 245 | 246 | if (!node) { 247 | // didn't find a parent or a placeholder, create a placeholder 248 | // node, i.e. a MutalbeArray, and add this logger as a child 249 | // 250 | _repository[keyPath] = [NSMutableArray arrayWithObject:aLogger]; 251 | 252 | } else if ([node isKindOfClass:[L4Logger class]]) { 253 | parent = node; 254 | break; 255 | 256 | } else if ([node isKindOfClass:[NSMutableArray class]]) { 257 | [node addObject:aLogger]; // found a place holder node, add this logger as a child 258 | } 259 | } 260 | aLogger.parent = parent; // found a parent, set it for this logger 261 | } 262 | 263 | - (void) updateChildren:(NSArray *)node withParent:(L4Logger *)newLogger 264 | { 265 | for (L4Logger *child in node) { 266 | // If the child's parent's name starts with the name of the new logger then its a child of the new logger leave the child alone, we'll get 267 | // to the child's parent in this list. Otherwise the parent is higher & insert the new logger. All children that go through this same 268 | // middle node, that don't have an already lower parent, should all point to the same higher parent. If not, something is wrong. 269 | // 270 | if (![child.parent.name hasPrefix:newLogger.name] ) { 271 | newLogger.parent = child.parent; 272 | child.parent = newLogger; 273 | } 274 | } 275 | } 276 | 277 | @end 278 | 279 | -------------------------------------------------------------------------------- /Log4Cocoa/L4Logging.h: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | #import 5 | #import "Log4CocoaDefines.h" 6 | #import "L4Level.h" 7 | 8 | /** 9 | * LOGGING MACROS: These macros are convience macros that easily allow the capturing of 10 | * line number, source file, and method name information without interupting the flow of 11 | * your source code. 12 | * 13 | * The base macros are not meant to be used directly; they are there for the other 14 | * to use as they define the basic function call. 15 | */ 16 | 17 | LOG4COCOA_EXTERN void log4Log(id object, int line, const char *file, const char *method, SEL sel, L4Level *level, BOOL isAssertion, 18 | BOOL assertion, id exception, id message, ...); 19 | 20 | 21 | #pragma mark - Base macros used for logging from objects 22 | 23 | #define L4_LOG(type, e) self, __LINE__, __FILE__, __PRETTY_FUNCTION__, @selector(lineNumber:fileName:methodName:message:level:exception:), type, NO, YES, e 24 | #define L4_ASSERTION(assertion) self, __LINE__, __FILE__, __PRETTY_FUNCTION__, @selector(lineNumber:fileName:methodName:assert:log:), [L4Level error], YES, assertion, nil 25 | 26 | 27 | #pragma mark - Base macros used for logging from C functions 28 | 29 | #define L4C_LOG(type, e) [L4FunctionLogger instance], __LINE__, __FILE__, __PRETTY_FUNCTION__, @selector(lineNumber:fileName:methodName:message:level:exception:), type, NO, YES, e 30 | #define L4C_ASSERTION(assertion) [L4FunctionLogger instance], __LINE__, __FILE__, __PRETTY_FUNCTION__, @selector(lineNumber:fileName:methodName:assert:log:), [L4Level error], YES, assertion, nil 31 | 32 | 33 | #pragma mark - Macros that log from objects 34 | 35 | #define log4Trace(message, ...) do{ if([[self l4Logger] isDebugEnabled]){ log4Log(L4_LOG([L4Level trace], nil), message, ##__VA_ARGS__);} }while(0) 36 | #define log4Debug(message, ...) do{ if([[self l4Logger] isDebugEnabled]){ log4Log(L4_LOG([L4Level debug], nil), message, ##__VA_ARGS__);} }while(0) 37 | #define log4Info(message, ...) do{ if([[self l4Logger] isInfoEnabled]){ log4Log(L4_LOG([L4Level info], nil), message, ##__VA_ARGS__);} }while(0) 38 | #define log4Warn(message, ...) do{ log4Log(L4_LOG([L4Level warn], nil), message, ##__VA_ARGS__); }while(0) 39 | #define log4Error(message, ...) do{ log4Log(L4_LOG([L4Level error], nil), message, ##__VA_ARGS__); }while(0) 40 | #define log4Fatal(message, ...) do{ log4Log(L4_LOG([L4Level fatal], nil), message, ##__VA_ARGS__); }while(0) 41 | 42 | 43 | #pragma mark - Macros that log from C functions 44 | 45 | #define log4CDebug(message, ...) do{ if([[[L4FunctionLogger instance] l4Logger] isDebugEnabled]){ log4Log(L4C_LOG([L4Level debug], nil), message, ##__VA_ARGS__);} }while(0) 46 | #define log4CInfo(message, ...) do{ if([[[L4FunctionLogger instance] l4Logger] isInfoEnabled]){ log4Log(L4C_LOG([L4Level info], nil), message, ##__VA_ARGS__);} }while(0) 47 | #define log4CWarn(message, ...) do{ log4Log(L4C_LOG([L4Level warn], nil), message, ##__VA_ARGS__); }while(0) 48 | #define log4CError(message, ...) do{ log4Log(L4C_LOG([L4Level error], nil), message, ##__VA_ARGS__); }while(0) 49 | #define log4CFatal(message, ...) do{ log4Log(L4C_LOG([L4Level fatal], nil), message, ##__VA_ARGS__); }while(0) 50 | 51 | 52 | #pragma mark - Macros that log with an exception from objects 53 | 54 | #define log4DebugWithException(message, e, ...) do{ if([[self l4Logger] isDebugEnabled]){ log4Log(L4_LOG([L4Level debug], e), message, ##__VA_ARGS__);} }while(0) 55 | #define log4InfoWithException(message, e, ...) do{ if([[self l4Logger] isInfoEnabled]){ log4Log(L4_LOG([L4Level info], e), message, ##__VA_ARGS__);} }while(0) 56 | #define log4WarnWithException(message, e, ...) do{ log4Log(L4_LOG([L4Level warn], e), message, ##__VA_ARGS__); }while(0) 57 | #define log4ErrorWithException(message, e, ...) do{ log4Log(L4_LOG([L4Level error], e), message, ##__VA_ARGS__); }while(0) 58 | #define log4FatalWithException(message, e, ...) do{ log4Log(L4_LOG([L4Level fatal], e), message, ##__VA_ARGS__); }while(0) 59 | 60 | 61 | #pragma mark Macros that log with an exception from C functions 62 | 63 | #define log4CDebugWithException(message, e, ...) do{ if([[[L4FunctionLogger instance] l4Logger] isDebugEnabled]){ log4Log(L4C_LOG([L4Level debug], e), message, ##__VA_ARGS__);} }while(0) 64 | #define log4CInfoWithException(message, e, ...) do{ if([[[L4FunctionLogger instance] l4Logger] isInfoEnabled]){ log4Log(L4C_LOG([L4Level info], e), message, ##__VA_ARGS__);} }while(0) 65 | #define log4CWarnWithException(message, e, ...) do{ log4Log(L4C_LOG([L4Level warn], e), message, ##__VA_ARGS__); }while(0) 66 | #define log4CErrorWithException(message, e, ...) do{ log4Log(L4C_LOG([L4Level error], e), message, ##__VA_ARGS__); }while(0) 67 | #define log4CFatalWithException(message, e, ...) do{ log4Log(L4C_LOG([L4Level fatal], e), message, ##__VA_ARGS__); }while(0) 68 | 69 | 70 | #pragma mark - Macro that log when an assertion is false from objects 71 | 72 | #define log4Assert(assertion, message, ...) do{ log4Log(L4_ASSERTION(assertion), message, ##__VA_ARGS__); }while(0) 73 | 74 | 75 | #pragma mark - Macro that log when an assertion is false from C functions 76 | 77 | #define log4CAssert(assertion, message, ...) do{ log4Log(L4C_ASSERTION(assertion), message, ##__VA_ARGS__); }while(0) 78 | 79 | -------------------------------------------------------------------------------- /Log4Cocoa/L4Logging.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import 6 | #import "L4Logging.h" 7 | #import "NSObject+Log4Cocoa.h" 8 | 9 | void log4Log(id object, int line, const char *file, const char *method, SEL sel, L4Level *level, BOOL isAssertion, BOOL assertion, id exception, id message, ...) 10 | { 11 | NSString *combinedMessage; 12 | if ([message isKindOfClass:[NSString class]] ) { 13 | va_list args; 14 | va_start(args, message); 15 | combinedMessage = [[NSString alloc] initWithFormat:message arguments:args]; 16 | va_end(args); 17 | } else { 18 | combinedMessage = message; 19 | } 20 | 21 | if (isAssertion) { 22 | objc_msgSend([object l4Logger], sel, line, file, method, assertion, combinedMessage); 23 | } else { 24 | objc_msgSend([object l4Logger], sel, line, file, method, combinedMessage, level, exception); 25 | } 26 | 27 | } 28 | 29 | -------------------------------------------------------------------------------- /Log4Cocoa/L4PatternLayout.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "L4Layout.h" 3 | 4 | /** 5 | *Defines the default conversion pattern for L4PatternLayout objects created with the init method 6 | */ 7 | extern NSString* const L4PatternLayoutDefaultConversionPattern; 8 | 9 | /** 10 | * Defines the name for invalid format specifier exceptions 11 | */ 12 | extern NSString* const L4InvalidSpecifierException; 13 | 14 | /** 15 | *Defines the name for the no conversion pattern exception 16 | * This exception is thrown if you try to use an L4PatternLayout before setting its conversion pattern 17 | */ 18 | extern NSString* const L4NoConversionPatternException; 19 | 20 | /** 21 | * Defines the name for the invalid brace clause exception 22 | * Some of the format specifiers can be followed by content surrounded by braces ({}). When this brace clause is invalid for 23 | * any reason, the L4InvalidBraceClauseException is thrown 24 | */ 25 | extern NSString* const L4InvalidBraceClauseException; 26 | 27 | /** 28 | * An NSCharacterSet that contains the default format specifier characters 29 | */ 30 | #define L4PatternLayoutDefaultSpecifiers [NSCharacterSet characterSetWithCharactersInString:@"CdFlLmMnprt%"] 31 | 32 | /** 33 | * An NSCharacterSet that contains the subset of characters from L4PatternLayoutDefaultSpecifiers that can have a brace clause 34 | * after the character 35 | */ 36 | #define L4PatternLayoutTrailingBracesSpecifiers [NSCharacterSet characterSetWithCharactersInString:@"Cd"] 37 | 38 | @class L4LogEvent; 39 | @class L4PatternParser; 40 | 41 | /** 42 | * A layout that uses a conversion pattern to format logging messages 43 | * 44 | * A flexible layout configurable with pattern string. 45 | * The goal of this class is to format a LoggingEvent and return the results as a String. The results 46 | * depend on the conversion pattern. 47 | * 48 | * The conversion pattern is closely related to the conversion pattern of the printf function in C. A conversion pattern is 49 | * composed of literal text and format control expressions called format specifiers. 50 | * You are free to insert any literal text within the conversion pattern. 51 | * 52 | * Each conversion specifier starts with a percent sign (%) and is followed by optional format modifiers and a specifier 53 | * character. The specifier character specifies the type of data, e.g. priority, date. The format modifiers control such things 54 | * as field width, padding, left and right justification. The following is a simple example. 55 | * 56 | * Let the conversion pattern be "%-5p :%m%n" and assume that the Log4Cocoa environment was set to use an L4PatternLayout. Then the 57 | * statements 58 | *
 59 |  * log4Debug("Message 1");
 60 |  * log4Warn("Message 2");
 61 |  * 
62 | * would yield the output 63 | *
 64 |  * DEBUG :Message 1
 65 |  * WARN  :Message 2
 66 |  * 
67 | * 68 | * Note that there is no explicit separator between text and conversion specifiers. The pattern parser knows when it has reached 69 | * the end of a conversion specifier when it reads a specifier character. In the example, above the conversion specifier 70 | * %-5p means the priority of the logging event should be left justified to a width of five characters. 71 | * 72 | * The recognized conversion characters are: 73 | 74 | * 75 | * 76 | * 77 | * 78 | * 79 | * 80 | * 81 | * 89 | * 90 | * 91 | * 92 | * 93 | * 94 | * 101 | * 102 | * 103 | * 104 | * 105 | * 106 | * 107 | * 108 | * 109 | * 110 | * 115 | * 116 | * 117 | * 118 | * 119 | * 120 | * 121 | * 122 | * 123 | * 124 | * 125 | * 126 | * 127 | * 128 | * 129 | * 130 | * 131 | * 132 | * 133 | * 134 | * 135 | * 136 | * 137 | * 138 | * 139 | * 140 | * 141 | * 142 | * 143 | * 144 | * 145 | * 146 | * 147 | * 148 | * 149 | * 150 | * 151 | * 152 | * 153 | * 154 | * 156 | * 157 | * 158 | *
Conversion CharacterEffect
CUsed to output the fully qualified class name of the logger issuing the logging request. This conversion specifier 82 | * can be optionally followed by precision specifier, thatis a decimal constant in braces. 83 | * 84 | * If a precision specifier is given, then only the corresponding number of right most components of the logger name will be 85 | * printed. By default the logger name is output in fully qualified form. 86 | * 87 | * For example, for the class name "NSObject.MyClass.SubClass.SomeClass", the pattern %C{1} will output "SomeClass". 88 | *
dUsed to output the date of the logging event. The date conversion specifier may be followed by a date format specifier 95 | * enclosed between braces. For example, %d{%H:%M:%S}. If no date format specifier is given, then an international format of 96 | * "%Y-%m-%d %H:%M:%S %z" is used. 97 | * 98 | * The date format specifier admits the same syntax as the calendar format string in NSCalendarDate's 99 | * descriptionWithCalendarFormat:method. 100 | *
FUsed to output the file name where the logging request was issued.
lUsed to output location information of the caller which generated the logging event. 111 | * 112 | * The location information consists of the fully qualified name of the calling logger, the method the log request originated in, 113 | * followed by the log request's source file name and line number between parentheses. 114 | *
LUsed to output the line number from where the logging request was issued.
mUsed to output the application supplied message associated with the logging event.
MUsed to output the method name where the logging request was issued.
nOutputs the line separator character, \\n.
pUsed to output the priority (level) of the logging event.
rUsed to output the number of milliseconds elapsed since the start of the application until the creation of the logging event.
tUsed to output the name of the thread where the logging event occured.
%The sequence %% outputs a single percent sign. 155 | *
159 | * 160 | * By default the relevant information is output as is. However, with the aid of format modifiers it is possible to change the 161 | * minimum field width, the maximum field width and justification. 162 | * 163 | * The optional format modifier is placed between the percent sign and the conversion character. 164 | * 165 | * The first optional format modifier is the left justification flag which is just the minus (-) character. Then comes the 166 | * optional minimum field width modifier. This is a decimal constant that represents the minimum number of characters to 167 | * output. If the data item requires fewer characters, it is padded on either the left or the right until the minimum width is 168 | * reached. The default is to pad on the left (right justify) but you can specify right padding with the left justification flag. The 169 | * padding character is space. If the data item is larger than the minimum field width, the field is expanded to accommodate the 170 | * data. The value is never truncated. 171 | * 172 | * This behavior can be changed using the maximum field width modifier which is designated by a period followed by a 173 | * decimal constant. If the data item is longer than the maximum field, then the extra characters are removed from the 174 | * beginning of the data item and not from the end. For example, it the maximum field width is eight and the data item is 175 | * ten characters long, then the first two characters of the data item are dropped. This behavior deviates from the printf function in C 176 | * where truncation is done from the end. 177 | * 178 | * Below are various format modifier examples for the category conversion specifier. 179 | * 180 | * 181 | * 182 | * 183 | * 184 | * 185 | * 186 | * 187 | * 188 | * 189 | * 190 | * 191 | * 192 | * 193 | * 194 | * 195 | * 196 | * 197 | * 198 | * 199 | * 200 | * 201 | * 202 | * 203 | * 204 | * 205 | * 206 | * 207 | * 208 | * 209 | * 210 | * 211 | * 212 | * 213 | * 214 | * 215 | * 216 | * 217 | * 218 | * 220 | * 221 | * 222 | * 223 | * 224 | * 225 | * 226 | * 227 | * 229 | * 230 | *
Format modifierleft justifyminimum widthmaximum widthcomment
%20Cfalse20noneLeft pad with spaces if the logger name is less than 20 characters long.
%-20Ctrue20noneRight pad with spaces if the logger name is less than 20 characters long.
%.30CNAnone30Truncate from the beginning if the logger name is longer than 30 characters.
%20.30Cfalse2030Left pad with spaces if the logger name is shorter than 20 characters. However, if logger name is longer than 30 characters, 219 | * then truncate from the beginning.
%-20.30Ctrue2030Right pad with spaces if the logger name is shorter than 20 characters. However, if logger name is longer than 30 characters, 228 | * then truncate from the beginning.
231 | * 232 | */ 233 | 234 | @protocol L4PatternLayoutConverterDelegate; 235 | @protocol L4PatternLayoutParserDelegate; 236 | 237 | @interface L4PatternLayout :L4Layout 238 | 239 | /** 240 | * Layout conversion pattern 241 | */ 242 | @property (copy) NSString * conversionPattern; 243 | 244 | /** 245 | * When the pattern layout formats logging messages, it first takes the conversion 246 | * pattern and parses it into token strings. Then, it takes each token string and 247 | * converts it into the corresponding part of the final formatted message. 248 | * You can provide a converter delegate to override or extend how a pattern layout 249 | * converts its token strings. By default, the pattern layout does nothing to 250 | * literal string tokens and converts format specifiers as explained in the description of this class. 251 | * The converter delegate must respond to the convertTokenString:withLoggingEvent:intoString:method. 252 | */ 253 | @property (strong) id converterDelegate; 254 | 255 | /** 256 | * When the pattern layout formats logging messages, it first takes the conversion pattern and parses it into token strings. 257 | * You can provide a parser delegate to override how a pattern layout parses the conversion pattern into token strings. By 258 | * default, the pattern layout divides the conversion pattern into a series of literal strings and format specifiers. 259 | * The parser delegate must respond to the parseConversionPattern:intoArray:method. 260 | */ 261 | @property (strong) id parserDelegate; 262 | 263 | /** 264 | * Initializes an L4PatternLayout with the default conversion pattern, %m%n 265 | * Calls initWthConversionPattern:with the string defined by L4PatternLayoutDefaultConversionPattern 266 | * @return A newly initialized L4PatternLayout object 267 | */ 268 | - (id)init; 269 | 270 | /** 271 | * Initializes an instance from properties. The properties supported are: 272 | * - ConversionPattern: the conversion pattern to use in this instance. 273 | * @param initProperties the proterties to use. 274 | */ 275 | - (id) initWithProperties:(L4Properties *)initProperties; 276 | 277 | /** 278 | * Initializes an L4PatternLayout with a custom conversion pattern. 279 | * @param aConversionPattern The custom conversion pattern. 280 | * @return A newly initialized L4PatternLayout object. 281 | */ 282 | - (id)initWithConversionPattern:(NSString*)aConversionPattern; 283 | 284 | /** 285 | * Uses this class's conversion pattern to format logging messages 286 | * @param event A logging event that contains information that the layout needs to format the logging message. 287 | * @throw L4InvalidSpecifierException if the pattern layout's conversion pattern contains an invalid conversion specifier. 288 | * @throw L4NoConversionPatternException if the pattern layout's conversion pattern is nil. This should only happen if you 289 | * have code like the following:[patternLayout setConversionPattern:nil]; 290 | * @throw L4InvalidBraceClauseException when a conversion specifier's brace clause is invalid for any reason. 291 | * @return A formatted logging message that adheres to the L4PatternLayout's conversion pattern. 292 | */ 293 | - (NSString *)format:(L4LogEvent *)event; 294 | 295 | @end 296 | 297 | /* 298 | * This is here for testing purposes. Otherwise, the conversion pattern does not get parsed until a log event happens. 299 | */ 300 | @interface L4PatternLayout (TestMethods) 301 | 302 | - (NSArray *)tokenArray; 303 | 304 | @end 305 | 306 | /** 307 | * Declares methods that an L4PatternLayout's converter delegate must implement. 308 | */ 309 | @protocol L4PatternLayoutConverterDelegate 310 | 311 | /** 312 | * Allows an object to override or extend how an L4PatternLayout converts its token strings into pieces of the final formatted. 313 | * logging message. 314 | * @param token The token string to convert 315 | * @param logEvent An L4LoggingEvent that contains information about the current logging request 316 | * @param convertedString A reference to an NSString that will contain the result of the token string's conversion upon exiting the method 317 | * @return Return YES to indicate that you have converted the token string. Return NO to let the pattern layout's default behavior attempt to convert it. 318 | */ 319 | - (BOOL)convertTokenString:(NSString*)token withLoggingEvent:(L4LogEvent*)logEvent intoString:(NSString**)convertedString; 320 | 321 | @end 322 | 323 | /** 324 | * Declares methods that an L4PatternLayout's parser delegate must implement. 325 | */ 326 | @protocol L4PatternLayoutParserDelegate 327 | 328 | /** 329 | * Allows an object to override how an L4PatternLayout parses its conversion pattern into a series of token strings. 330 | * @param cp The conversion pattern to be parsed 331 | * @param tokenStringArray A mutable array to hold the parsed tokens 332 | */ 333 | - (void)parseConversionPattern:(NSString*)cp intoArray:(NSMutableArray* __autoreleasing *)tokenStringArray; 334 | 335 | @end 336 | 337 | // For copyright & license, see LICENSE. 338 | -------------------------------------------------------------------------------- /Log4Cocoa/L4Properties.h: -------------------------------------------------------------------------------- 1 | // For copyright & license, see LICENSE. 2 | 3 | #import 4 | 5 | /** 6 | *Defines the name for the no conversion pattern exception 7 | * This exception is thrown if you try to use an L4PatternLayout before setting its conversion pattern 8 | */ 9 | extern NSString* const L4PropertyMissingException; 10 | 11 | /** 12 | * The L4Properties class provides a dictionary like interface to properties used to configure the logging system. Even 13 | * though these properties would typically be read in from a file, they can also be set from an NSDictionary. 14 | */ 15 | @interface L4Properties : NSObject 16 | 17 | /** 18 | * Returns an empty properties instance. 19 | * @return the newly created, empty properties instance. 20 | */ 21 | + (instancetype)properties; 22 | 23 | /** 24 | * Creates and returns a new instance, reading in properties from the specified file. 25 | * @param aName the name of the file containing the properties to use. 26 | * @return the newly created, configured properties instance. 27 | */ 28 | + (instancetype)propertiesWithFileName:(NSString *) aName; 29 | 30 | /** 31 | * Creates and returns a new instance, using the supplied dictionary for the property values. 32 | * @param aProperties the dictionary containing the properties to use. 33 | * @return the newly created, configured properties instance. 34 | */ 35 | + (instancetype)propertiesWithProperties:(NSDictionary *) aProperties; 36 | 37 | /** 38 | * Returns all the keys in this property list. 39 | * @return an array of all the keys in this property list. 40 | */ 41 | - (NSArray *)allKeys; 42 | 43 | /** 44 | * Accessor for the int value of the number of entries in this collection. 45 | * @return the int value of the number of entries in this collection. 46 | */ 47 | - (NSUInteger)count; 48 | 49 | /** 50 | * This method initializes a new instance of this class with the specified file path. 51 | * @param aName the file path of the properties file you want to read from. 52 | * @return An initialized instance of this class. 53 | */ 54 | - (id)initWithFileName:(NSString *)aName; 55 | 56 | /** 57 | * This method initializes a new instance of this class with the specified dictionary. 58 | * @param aProperties an initialized dictionary that contains the properties you want. 59 | * @return An initialized instance of this class. 60 | */ 61 | - (id)initWithProperties:(NSDictionary *)aProperties; 62 | 63 | /** 64 | * Removes the property indexed by key from this property list. 65 | * @param aKey the name of the property which is to be removed from the property list. 66 | */ 67 | - (void)removeStringForKey:(NSString *)aKey; 68 | 69 | /** 70 | * Searches for the property with the specified key in this property list. If the key is not found in this property 71 | * list, the default property list, and its defaults, recursively, are then checked. 72 | * @param aKey the name of the property which is requested. 73 | * @return the value of the requested property or nil if the specified property was not found. 74 | */ 75 | - (NSString *)stringForKey:(NSString *)aKey; 76 | 77 | /** 78 | * Searches for the property with the specified key in this property list. If the key is not found in this property 79 | * list, the default property list, and its defaults, recursively, are then checked. 80 | * @param aKey the name of the property which is requested. 81 | * @param aDefaultValue the default value to be returned if the specified property was not found. 82 | * @return the value of the requested property or the default value argument if the specified property was not found. 83 | */ 84 | - (NSString *)stringForKey:(NSString *)aKey withDefaultValue:(NSString *)aDefaultValue; 85 | 86 | /** 87 | * Inserts aString into this property list indexed by aKey. 88 | * @param aString the value for the property to be inserted. 89 | * @param aKey the name of the property which is to be inserted into the property list. 90 | */ 91 | - (void)setString:(NSString *)aString forKey:(NSString *)aKey; 92 | 93 | /** 94 | * Returns a subset of the properties whose keys start with the specified prefix. 95 | * The returned properties have the specified prefix trimmed from their keys. 96 | * @param aPrefix the property name prefix to search for, and remove from the returned property list subset. 97 | * @return a subset of the original property list which contains all the properties whose keys started with the 98 | * specified prefix, but have had the prefix trimmed from them. 99 | */ 100 | - (L4Properties *)subsetForPrefix:(NSString *)aPrefix; 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /Log4Cocoa/L4Properties.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4Properties.h" 6 | #import "L4LogLog.h" 7 | 8 | static NSString *L4PropertiesCommentChar = @"#"; 9 | static NSString *DELIM_START = @"${"; 10 | static int DELIM_START_LEN = 2; 11 | static NSString *DELIM_STOP = @"}"; 12 | static int DELIM_STOP_LEN = 1; 13 | NSString* const L4PropertyMissingException = @"L4PropertyMissingException"; 14 | 15 | /** 16 | * Private messages for the L4Properties class. 17 | */ 18 | @interface L4Properties () 19 | /** 20 | * A helper method that enumerates all the stored properties, and for each one, 21 | * invokes the substituteEnvironmentVariablesForString: helper method on both 22 | * its key and its value. 23 | */ 24 | - (void) replaceEnvironmentVariables; 25 | /** 26 | * A helper method that takes a string that may contain any number of named 27 | * environment variable references, such as ${TEMP_DIR} and substitutes each 28 | * named environment variable reference with its actual value instead. 29 | * 30 | * @param aString a string that may contain zero or more named environment variable references 31 | * @return a copy of the input string, with all the named environment variable references 32 | * having been replaced by their actual values. 33 | */ 34 | - (NSString *) substituteEnvironmentVariablesForString:(NSString *)aString; 35 | @end 36 | 37 | @implementation L4Properties { 38 | NSMutableDictionary *_properties; /**< The internal dictionary in which individual properties are stored.*/ 39 | } 40 | 41 | #pragma mark - Class methods 42 | 43 | + (instancetype)properties 44 | { 45 | return [self propertiesWithProperties:[NSMutableDictionary dictionary]]; 46 | } 47 | 48 | + (instancetype)propertiesWithFileName:(NSString *)aName 49 | { 50 | return [[L4Properties alloc] initWithFileName:aName]; 51 | } 52 | 53 | + (instancetype)propertiesWithProperties:(NSDictionary *)aProperties 54 | { 55 | return [[L4Properties alloc] initWithProperties:aProperties]; 56 | } 57 | 58 | 59 | #pragma mark - Instance methods 60 | 61 | - (NSArray *) allKeys 62 | { 63 | return [_properties allKeys]; 64 | } 65 | 66 | - (NSUInteger) count 67 | { 68 | return [_properties count]; 69 | } 70 | 71 | - (NSString *) description 72 | { 73 | return [_properties description]; 74 | } 75 | 76 | - (id) init 77 | { 78 | return [self initWithFileName:nil]; 79 | } 80 | 81 | - (id) initWithFileName:(NSString *)aName 82 | { 83 | self = [super init]; 84 | if (self) { 85 | _properties = [NSMutableDictionary dictionary]; 86 | 87 | NSString *fileContents = [NSString stringWithContentsOfFile:aName encoding:NSUTF8StringEncoding error:nil]; 88 | 89 | NSEnumerator *lineEnumerator = [[fileContents componentsSeparatedByString:@"\n"] objectEnumerator]; 90 | NSString *currentLine = nil; 91 | while ( ( currentLine = [lineEnumerator nextObject] ) != nil ) { 92 | currentLine = [currentLine stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 93 | 94 | if ( ![currentLine hasPrefix:L4PropertiesCommentChar] ) { 95 | NSRange range = [currentLine rangeOfString:@"="]; 96 | 97 | if ( ( range.location != NSNotFound ) && ( [currentLine length] > range.location + 1 ) ) { 98 | _properties[[currentLine substringToIndex:range.location]] = [currentLine substringFromIndex:range.location + 1]; 99 | } 100 | } 101 | } 102 | [self replaceEnvironmentVariables]; 103 | } 104 | 105 | return self; 106 | } 107 | 108 | - (id) initWithProperties:(NSDictionary *)aProperties 109 | { 110 | self = [super init]; 111 | if (self) { 112 | _properties = [[NSMutableDictionary alloc] initWithDictionary:aProperties]; 113 | } 114 | 115 | return self; 116 | } 117 | 118 | - (void) removeStringForKey:(NSString *)aKey 119 | { 120 | [_properties removeObjectForKey:aKey]; 121 | } 122 | 123 | - (void) setString:(NSString *)aString forKey:(NSString *)aKey 124 | { 125 | _properties[aKey] = aString; 126 | [self replaceEnvironmentVariables]; 127 | } 128 | 129 | - (NSString *) stringForKey:(NSString *)aKey 130 | { 131 | return [self stringForKey:aKey withDefaultValue:nil]; 132 | } 133 | 134 | - (NSString *) stringForKey:(NSString *)aKey withDefaultValue:(NSString *)aDefaultValue 135 | { 136 | return _properties[aKey] ?: aDefaultValue; 137 | } 138 | 139 | - (L4Properties *) subsetForPrefix:(NSString *)aPrefix 140 | { 141 | NSMutableDictionary *subset = [NSMutableDictionary dictionaryWithCapacity:[_properties count]]; 142 | 143 | NSEnumerator *keyEnum = [[_properties allKeys] objectEnumerator]; 144 | NSString *key = nil; 145 | while ( ( key = [keyEnum nextObject] ) != nil ) { 146 | NSRange range = [key rangeOfString:aPrefix options:0 range:NSMakeRange(0, [key length])]; 147 | if ( range.location != NSNotFound ) { 148 | NSString *subKey = [key substringFromIndex:range.length]; 149 | subset[subKey] = _properties[key]; 150 | } 151 | } 152 | 153 | return [L4Properties propertiesWithProperties:subset]; 154 | } 155 | 156 | 157 | #pragma mark - Private methods 158 | 159 | - (void) replaceEnvironmentVariables 160 | { 161 | NSEnumerator *keyEnum = [[self allKeys] objectEnumerator]; 162 | NSString *key = nil; 163 | while ( ( key = [keyEnum nextObject] ) != nil ) { 164 | NSString *value = [self stringForKey:key]; 165 | NSString *subKey = [self substituteEnvironmentVariablesForString:key]; 166 | if ( ![subKey isEqualToString:key] ) { 167 | [self removeStringForKey:key]; 168 | [self setString:subKey forKey:value]; 169 | } 170 | NSString *subVal = [self substituteEnvironmentVariablesForString:value]; 171 | if ( ![subVal isEqualToString:value] ) { 172 | [self setString:subVal forKey:subKey]; 173 | } 174 | } 175 | } 176 | 177 | - (NSString *) substituteEnvironmentVariablesForString:(NSString *)aString 178 | { 179 | int len = [aString length]; 180 | NSMutableString *buf = [NSMutableString string]; 181 | NSRange i = NSMakeRange(0, len); 182 | NSRange j, k; 183 | while ( true ) { 184 | j = [aString rangeOfString:DELIM_START options:0 range:i]; 185 | if ( j.location == NSNotFound ) { 186 | if ( i.location == 0 ) { 187 | return aString; 188 | } else { 189 | [buf appendString:[aString substringFromIndex:i.location]]; 190 | return buf; 191 | } 192 | } else { 193 | [buf appendString:[aString substringWithRange:NSMakeRange(i.location, j.location - i.location)]]; 194 | k = [aString rangeOfString:DELIM_STOP options:0 range:NSMakeRange(j.location, len - j.location)]; 195 | if ( k.location == NSNotFound ) { 196 | [L4LogLog error: 197 | [NSString stringWithFormat:@"\"%@\" has no closing brace. Opening brace at position %@.", 198 | aString, [NSNumber numberWithInt:j.location]]]; 199 | return aString; 200 | } else { 201 | j.location += DELIM_START_LEN; 202 | j = NSMakeRange(j.location, k.location - j.location); 203 | NSString *key = [aString substringWithRange:j]; 204 | char *replacement = getenv([key UTF8String]); 205 | if ( replacement != NULL ) { 206 | [buf appendString:@(replacement)]; 207 | } 208 | i.location += (k.location + DELIM_STOP_LEN); 209 | i.length -= i.location; 210 | } 211 | } 212 | } 213 | } 214 | 215 | @end 216 | -------------------------------------------------------------------------------- /Log4Cocoa/L4PropertyConfigurator.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class L4Properties; 4 | 5 | /** 6 | * The class responsible for configuring the logging system with a properties file. 7 | */ 8 | @interface L4PropertyConfigurator : NSObject { 9 | L4Properties* properties; /**< The internal collection in which individual properties are stored.*/ 10 | NSMutableDictionary* appenders; /**< The internal collection in which individual configured appenders are stored.*/ 11 | } 12 | 13 | /** 14 | * Returns a newly created instance of L4PropertyConfigurator set to use a given file name for the properties. 15 | * @param aName the name of the file to read the properties from, 16 | * @return the new instance of L4PropertyConfigurator. 17 | */ 18 | + (instancetype)propertyConfiguratorWithFileName:(NSString *)aName; 19 | 20 | /** 21 | * Returns a newly created instance of L4PropertyConfigurator set to use a given properties instance. 22 | * @param aProperties The properties to use in configuring the system. 23 | * @return the new instance of L4PropertyConfigurator. 24 | */ 25 | + (instancetype)propertyConfiguratorWithProperties:(L4Properties *)aProperties; 26 | 27 | /** 28 | * This method initializes a new instance of this class with the specified file path. 29 | * @param aName the file path of the properties file you want to read from. 30 | * @return An initialized instance of this class. 31 | */ 32 | - (id)initWithFileName:(NSString *) aName; 33 | 34 | /** 35 | * This method initializes a new instance of this class with the specified properties. 36 | * @param aProperties an initialized properties collection that contains the properties you want. 37 | * @return An initialized instance of this class. 38 | */ 39 | - (id)initWithProperties:(L4Properties *) aProperties; 40 | 41 | /** 42 | * Read configuration from a file. The existing configuration is not cleared nor reset. 43 | * 44 | * The configuration file consists of statements in the format key=value. 45 | * The syntax of different configuration elements are discussed below. 46 | * 47 | * Appender configuration 48 | * 49 | * Appender configuration syntax is: 50 | * 51 | * 52 | * # For appender named "appenderName", set its class.\n 53 | * log4cocoa.appender.appenderName=name_of_appender_class 54 | * 55 | * # Set appender specific options.\n 56 | * log4cocoa.appender.appenderName.option1=value1\n 57 | * ...\n 58 | * log4cocoa.appender.appenderName.optionN=valueN\n 59 | * 60 | * For each named appender you can configure its layout. The syntax for configuring an appender's layout is:\n 61 | * log4cocoa.appender.appenderName.layout=name_of_layout_class\n 62 | * log4cocoa.appender.appenderName.layout.option1=value1\n 63 | * ....\n 64 | * log4cocoa.appender.appenderName.layout.optionN=valueN 65 | * 66 | * Configuring loggers 67 | * 68 | * The syntax for configuring the root logger is:\n 69 | * log4cocoa.rootLogger=[LogLevel], appenderName, appenderName, ... 70 | * 71 | * This syntax means that an optional LogLevel value can be supplied followed by appender names separated by commas. 72 | * 73 | * The LogLevel value can consist of the string values FATAL, ERROR, WARN, INFO or DEBUG. 74 | * 75 | * If a LogLevel value is specified, then the root LogLevel is set to the corresponding LogLevel. If no LogLevel value 76 | * is specified, then the root LogLevel remains untouched. 77 | * 78 | * The root logger can be assigned multiple appenders. 79 | * 80 | * Each appenderName (separated by commas) will be added to the root logger. The named appender is defined using the 81 | * appender syntax defined above. 82 | * 83 | * For non-root loggers the syntax is almost the same:\n 84 | * log4cocoa.logger.logger_name=[LogLevel|INHERITED], appenderName, appenderName, ... 85 | * 86 | * The meaning of the optional LogLevel value is discussed above in relation to the root logger. In addition however, 87 | * the value INHERITED can be specified meaning that the named logger should inherit its LogLevel from the logger 88 | * hierarchy. 89 | * 90 | * By default loggers inherit their LogLevel from the hierarchy. However, if you set the LogLevel of a logger and 91 | * later decide that that logger should inherit its LogLevel, then you should specify INHERITED as the value for the 92 | * LogLevel value. 93 | * 94 | * Similar to the root logger syntax, each appenderName (separated by commas) will be attached to the named 95 | * logger. 96 | * 97 | * Example 98 | * 99 | * An example configuration is given below.\n 100 | * 101 | * # Set options for appender named "A1".\n 102 | * # Appender "A1" will be a L4ConsoleAppender\n 103 | * log4cocoa.appender.A1=L4ConsoleAppender 104 | * 105 | * # The console appender should write to standard out\n 106 | * log4cocoa.appender.A1.LogToStandardOut=true 107 | * 108 | * # A1's layout is a L4PatternLayout, using the conversion pattern "%-5p : %m%n". Thus, the log output will include:\n 109 | * # - the relative time since the start of the application in milliseconds, \n 110 | * # - followed by the LogLevel of the log request,\n 111 | * # - followed by the two rightmost components of the logger name,\n 112 | * # - followed by the callers method name, \n 113 | * # - followed by the line number,\n 114 | * # - the nested disgnostic context,\n 115 | * # - and finally the message itself.\n 116 | * # - Refer to the documentation of L4PatternLayout for further information on the syntax of the ConversionPattern 117 | * key.\n 118 | * log4cocoa.appender.A1.layout=L4PatternLayout\n 119 | * log4cocoa.appender.A1.layout.ConversionPattern=%-5p : %m%n 120 | * 121 | * # Set options for appender named "A2"\n 122 | * # A2 should be a RollingFileAppender, with maximum file size of 10 MB using at most one backup file. A2's layout is\n 123 | * #L4PatternLayout using the default conversion pattern.\n 124 | * log4cocoa.appender.A2=L4RollingFileAppender\n 125 | * log4cocoa.appender.A2.MaximumFileSize=10MB\n 126 | * log4cocoa.appender.A2.MaxBackupIndex=1\n 127 | * log4cocoa.appender.A2.layout=L4PatternLayout 128 | * 129 | * # Root logger set to DEBUG using the A2 appender defined above.\n 130 | * log4cocoa.rootLogger=DEBUG, A2 131 | * 132 | * # The logger "class_of_the_day" inherits its LogLevel from the\n 133 | * # logger hierarchy. Output will go to the appender's of the root\n 134 | * # logger, A2 in this case.\n 135 | * log4cocoa.logger.class_of_the_day=INHERIT 136 | * 137 | * 138 | * 139 | * Refer to the initWithProperties: method in each L4Appender and L4Layout for class specific properties. 140 | * 141 | * Use the # character at the beginning of a line for comments. 142 | */ 143 | - (void) configure; 144 | 145 | @end 146 | // For copyright & license, see LICENSE. 147 | -------------------------------------------------------------------------------- /Log4Cocoa/L4PropertyConfigurator.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4PropertyConfigurator.h" 6 | #import "L4AppenderProtocols.h" 7 | #import "L4Level.h" 8 | #import "L4LogLog.h" 9 | #import "L4Properties.h" 10 | #import "L4RootLogger.h" 11 | 12 | /** 13 | * Private methods for the L4PropertyConfigurator class. 14 | */ 15 | @interface L4PropertyConfigurator (Private) 16 | /** 17 | * Factory method to create a new L4Appender for a given class with configuratin properties. 18 | * @param appenderClassName the name of the class this appender is for. 19 | * @param appenderProperties the properties used to configure the new appender. 20 | * @return the newly configured appender. 21 | */ 22 | - (id)appenderForClassName:(NSString *)appenderClassName andProperties:(L4Properties *)appenderProperties; 23 | 24 | /** 25 | * Reads through the properties one at a time looking for properties related to additivity. If found, they are 26 | * used to configure the framework. 27 | */ 28 | - (void)configureAdditivity; 29 | 30 | /** 31 | * Reads through the properties one at a time looking for a named appender. If one is found, the properties 32 | * for that appender are seperated out, and sent to the appender to initialize & configure the appender. 33 | */ 34 | - (void)configureAppenders; 35 | 36 | /** 37 | * Reads through the properties one at a time looking for a named loggers. If one is found, the properties 38 | * for that logger are seperated out, and sent to the logger to initialize & configure the logger. 39 | */ 40 | - (void)configureLoggers; 41 | @end 42 | 43 | 44 | @implementation L4PropertyConfigurator 45 | 46 | 47 | #pragma mark - Class methods 48 | 49 | + (instancetype)propertyConfiguratorWithFileName:(NSString *)aName 50 | { 51 | return [[self alloc] initWithFileName: aName]; 52 | } 53 | 54 | + (instancetype) propertyConfiguratorWithProperties:(L4Properties *)aProperties 55 | { 56 | return [(L4PropertyConfigurator *) [self alloc] initWithProperties:aProperties]; 57 | } 58 | 59 | 60 | #pragma mark - Instance methods 61 | 62 | - (void) configure 63 | { 64 | @synchronized(self) { 65 | [self configureAppenders]; 66 | [self configureLoggers]; 67 | [self configureAdditivity]; 68 | 69 | // Erase the appenders to that we are not artificially retaining them. 70 | [appenders removeAllObjects]; 71 | } 72 | } 73 | 74 | - (void) configureLogger:(L4Logger *)aLogger withProperty:(NSString *)aProperty 75 | { 76 | // Remove all whitespace characters from config 77 | NSArray *components = [aProperty componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 78 | NSEnumerator *componentEnum = [components objectEnumerator]; 79 | NSString *component = nil; 80 | NSString *configString = @""; 81 | while ( ( component = [componentEnum nextObject] ) != nil ) { 82 | configString = [configString stringByAppendingString:component]; 83 | } 84 | 85 | // "Tokenize" configString 86 | components = [configString componentsSeparatedByString:@","]; 87 | 88 | if ( [components count] == 0 ) { 89 | [L4LogLog error:[NSString stringWithFormat:@"Invalid config string(Logger = %@): \"%@\".", [aLogger name], aProperty]]; 90 | return; 91 | } 92 | 93 | // Set the loglevel 94 | componentEnum = [components objectEnumerator]; 95 | NSString *logLevel = [[componentEnum nextObject] uppercaseString]; 96 | if ( ![logLevel isEqualToString:@"INHERITED"] ) { 97 | [aLogger setLevel:[L4Level levelWithName:logLevel]]; 98 | } 99 | 100 | // Set the Appenders 101 | while ( ( component = [componentEnum nextObject] ) != nil ) { 102 | id appender = appenders[component]; 103 | if ( appender == nil ) { 104 | [L4LogLog error:[NSString stringWithFormat:@"Invalid appender: \"%@\".", component]]; 105 | continue; 106 | } 107 | [aLogger addAppender:appender]; 108 | } 109 | } 110 | 111 | - (id) init 112 | { 113 | return nil; // never use this constructor 114 | } 115 | 116 | - (id) initWithFileName:(NSString *)aName 117 | { 118 | return [self initWithProperties:[L4Properties propertiesWithFileName:aName]]; 119 | } 120 | 121 | - (id) initWithProperties:(L4Properties *)initProperties 122 | { 123 | self = [super init]; 124 | if (self) { 125 | properties = [initProperties subsetForPrefix:@"log4cocoa."]; 126 | appenders = [[NSMutableDictionary alloc] init]; 127 | } 128 | 129 | return self; 130 | } 131 | 132 | 133 | #pragma mark - Private methods 134 | 135 | - (id) appenderForClassName:(NSString *)appenderClassName andProperties:(L4Properties *)appenderProperties 136 | { 137 | id newAppender = nil; 138 | Class appenderClass = NSClassFromString(appenderClassName); 139 | Protocol *appenderProtocol = @protocol(L4Appender); 140 | NSString *apenderProtocolName = NSStringFromProtocol(@protocol(L4Appender)); 141 | 142 | if ( appenderClass == nil ) { 143 | [L4LogLog error:[NSString stringWithFormat:@"Cannot find %@ class with name: \"%@\".", 144 | apenderProtocolName, appenderClassName]]; 145 | } else { 146 | if ( ![appenderClass conformsToProtocol: appenderProtocol] ) { 147 | [L4LogLog error: 148 | [NSString stringWithFormat: 149 | @"Failed to create instance with name \"%@\" since it does not conform to the %@ protocol.", 150 | apenderProtocolName, appenderProtocol]]; 151 | } else { 152 | newAppender = [(id )[appenderClass alloc] initWithProperties:appenderProperties]; 153 | } 154 | } 155 | return newAppender; 156 | } 157 | 158 | - (void) configureAdditivity 159 | { 160 | L4Properties *additivityProperties = [properties subsetForPrefix: @"additivity."]; 161 | 162 | NSEnumerator *keyEnum = [[additivityProperties allKeys] objectEnumerator]; 163 | NSString *key = nil; 164 | while ( ( key = [keyEnum nextObject] ) != nil ) { 165 | L4Logger *logger = [L4Logger loggerForName: key]; 166 | NSString *actualValue = [additivityProperties stringForKey: key]; 167 | NSString *value = [actualValue lowercaseString]; 168 | if ( [value isEqualToString: @"true"] ) { 169 | [logger setAdditivity: YES]; 170 | } else if ( [value isEqualToString: @"false"] ) { 171 | [logger setAdditivity: NO]; 172 | } else { 173 | [L4LogLog error: [NSString stringWithFormat: @"Invalid additivity value for logger %@: \"%@\".", key, actualValue]]; 174 | } 175 | } 176 | } 177 | 178 | - (void) configureAppenders 179 | { 180 | L4Properties *appendersProperties = [properties subsetForPrefix: @"appender."]; 181 | 182 | NSEnumerator *keyEnum = [[appendersProperties allKeys] objectEnumerator]; 183 | NSString *key = nil; 184 | while ( ( key = [keyEnum nextObject] ) != nil ) { 185 | NSRange range = [key rangeOfString:@"." options:0 range:NSMakeRange(0, [key length])]; 186 | if ( range.location == NSNotFound ) { 187 | // NSNotFound indicates that the key is for a new appender (i.e A1 - where there is no dot after the 188 | // appender name). We now need to get the subset of properties for the named appender. 189 | L4Properties *appenderProperties = [appendersProperties subsetForPrefix:[key stringByAppendingString:@"."]]; 190 | 191 | NSString *className = [appendersProperties stringForKey:key]; 192 | id newAppender = [self appenderForClassName:className andProperties:appenderProperties]; 193 | 194 | if ( newAppender != nil ) { 195 | [newAppender setName:key]; 196 | appenders[key] = newAppender; 197 | } else { 198 | [L4LogLog error: 199 | [NSString stringWithFormat: 200 | @"Error while creating appender \"%@\" with name \"%@\".", className, key]]; 201 | continue; 202 | } 203 | } 204 | } 205 | } 206 | 207 | - (void) configureLoggers 208 | { 209 | NSString *rootLoggerProperty = [properties stringForKey: @"rootLogger"]; 210 | if ( [properties stringForKey: @"rootLogger"] != nil ) { 211 | [self configureLogger: [L4Logger rootLogger] withProperty: rootLoggerProperty]; 212 | } 213 | 214 | L4Properties *loggerProperties = [properties subsetForPrefix: @"logger."]; 215 | NSEnumerator *keyEnum = [[loggerProperties allKeys] objectEnumerator]; 216 | NSString *key = nil; 217 | while ( ( key = [keyEnum nextObject] ) != nil ) { 218 | [self configureLogger: [L4Logger loggerForName: key] withProperty: [loggerProperties stringForKey: key]]; 219 | } 220 | } 221 | 222 | @end 223 | -------------------------------------------------------------------------------- /Log4Cocoa/L4RollingFileAppender.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "L4FileAppender.h" 3 | 4 | @class L4Layout; 5 | @class L4LogEvent; 6 | 7 | /** 8 | * The default maximum file size, which is 10MB. 9 | */ 10 | extern const unsigned long long kL4RollingFileAppenderDefaultMaxFileSize; 11 | 12 | /** 13 | * This class allows you to automatically create up to a number of backup log files when the log file reaches a 14 | * specified size. This class is a subclass of L4FileAppender. 15 | */ 16 | @interface L4RollingFileAppender : L4FileAppender 17 | 18 | /** 19 | * The maxBackupIndex determines how many backup files are kept before the oldest is erased. This method takes a 20 | * positive integer value. If set to zero, then there will be no backup files and the log file will be truncated when it 21 | * reaches maxFileSize. 22 | */ 23 | @property NSUInteger maxBackupIndex; 24 | 25 | /** 26 | * Returns the maximum file size allowed for log files. If the file grows larger than this, it will be backed up and 27 | * any additional logging statements will be written to a new file. 28 | * By default, this is 10MB. 29 | */ 30 | @property NSUInteger maxFileSize; 31 | 32 | /** 33 | * This method initializes a new instance of the L4RollingFileAppender class. 34 | * This method calls initWithLayout:fileName:append:, with the default values:nil, nil, and YES 35 | * respectively. 36 | * return An initialized instance of this class 37 | */ 38 | - (id) init; 39 | 40 | /** 41 | * Initializes an instance from properties. The properties supported are: 42 | * - MaximumFileSize: the maxamum size of the log file in bytes, before beginning a new one. A suffix of MB and 43 | * KB can be used to specify mega and kilo bytes. 44 | * - MaxBackupIndex: specifies how many log files to keep. 45 | * If the values are being set in a file, this is how they could look: 46 | * log4cocoa.appender.A2.MaximumFileSize=10MB 47 | * log4cocoa.appender.A2.MaxBackupIndex=3 48 | * This specifies that three files should be maintained, in adition to the one currently being logged to, and that the 49 | * file should be rolled at 10 MB in size. 50 | * @param initProperties the proterties to use. 51 | */ 52 | - (id) initWithProperties:(L4Properties *) initProperties; 53 | 54 | /** 55 | * This method initialized a new instance of this class with the specified layout and file path 56 | * This method calls initWithLayout:fileName:append:, with the values:aLayout, aName, and YES 57 | * respectively. 58 | * @param aLayout The layout object you want this appender to have. 59 | * @param aName The file path of the initial file you want created. Backup files have the same name, but with the 60 | * backup file number appended to it (See the rollOver method). 61 | * @return An initialized instance of this class. 62 | */ 63 | - (id) initWithLayout:(L4Layout *) aLayout fileName:(NSString *) aName; 64 | 65 | /** 66 | * This method initialized a new instance of this class with the specified layout, file path, and append option 67 | * This is the class's designated initializer. 68 | * @param aLayout The layout object you want this appender to have. 69 | * @param aName The file path of the initial file you want created. Backup files have the same name, but with the 70 | * backup file number appended to it (See the rollOver method). 71 | * @param flag YES = log output should be appended to the file. NO = the file's previous contents should be 72 | * overwritten. 73 | * @return An initialized instance of this class 74 | */ 75 | - (id) initWithLayout:(L4Layout *) aLayout fileName:(NSString *) aName append:(BOOL) flag; 76 | 77 | /** 78 | * Explicitly rolls a file over. 79 | * If maxBackupIndex is positive, then files {File.1, ..., File.MaxBackupIndex -1} are renamed to { 80 | * File.2, ..., File.MaxBackupIndex}. 81 | * Moreover, File is renamed File.1 and closed. A new file is created to receive further log output. 82 | * If maxBackupIndex is equal to zero, then the File is truncated with no backup files created. 83 | */ 84 | - (void)rollOver; 85 | 86 | @end 87 | 88 | /** 89 | *These methods are "protected" methods and should not be called except by subclasses. 90 | */ 91 | @interface L4RollingFileAppender (ProtectedMethods) 92 | 93 | /** 94 | * This method overrides the implementation in L4WriterAppender. It checks if the maximum file size has been exceeded. 95 | * If so, it rolls the file over according to the maxBackupIndex setting. 96 | * @param event An L4LoggingEvent that contains logging specific information 97 | */ 98 | - (void)subAppend:(L4LogEvent*)event; 99 | 100 | @end 101 | // For copyright & license, see LICENSE. 102 | -------------------------------------------------------------------------------- /Log4Cocoa/L4RollingFileAppender.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4RollingFileAppender.h" 6 | #import "L4Layout.h" 7 | #import "L4LogEvent.h" 8 | #import "L4LogLog.h" 9 | #import "L4Properties.h" 10 | 11 | const unsigned long long kL4RollingFileAppenderDefaultMaxFileSize = (1024 * 1024 * 10); 12 | 13 | /** 14 | * Private methods for the L4RollingFileAppender class. 15 | */ 16 | @interface L4RollingFileAppender () 17 | /** 18 | * Renames the current log file to have a specific index. Used for rolling log files. 19 | * @param backupIndex the index of the new file. 20 | */ 21 | - (void)renameLogFile:(unsigned int)backupIndex; 22 | 23 | @end 24 | 25 | 26 | @implementation L4RollingFileAppender 27 | 28 | - (id) init 29 | { 30 | return [self initWithLayout:nil fileName:nil append:YES]; 31 | } 32 | 33 | - (id) initWithProperties:(L4Properties *)initProperties 34 | { 35 | self = [super initWithProperties:initProperties]; 36 | 37 | if ( self != nil ) { 38 | unsigned int newMaxFileSize = kL4RollingFileAppenderDefaultMaxFileSize; 39 | unsigned int newMaxBackupIndex = 1; 40 | 41 | // Support for appender.MaximumFileSize in properties configuration file 42 | if ( [initProperties stringForKey:@"MaximumFileSize"] != nil ) { 43 | NSString *buf = [[initProperties stringForKey:@"MaximumFileSize"] uppercaseString]; 44 | newMaxFileSize = atoi([buf UTF8String]); 45 | if ( [buf rangeOfString:@"MB"].location == ([buf length] - 2) ) { 46 | newMaxFileSize *= (1024 * 1024); // convert to megabytes 47 | } 48 | if ( [buf rangeOfString:@"KB"].location == ([buf length] - 2) ) { 49 | newMaxFileSize *= 1024; // convert to kilobytes 50 | } 51 | } 52 | 53 | // Support for appender.MaxBackupIndex in properties configuration file 54 | if ( [initProperties stringForKey:@"MaxBackupIndex"] != nil ) { 55 | NSString *buf = [[initProperties stringForKey:@"MaxBackupIndex"] uppercaseString]; 56 | newMaxBackupIndex = atoi([buf UTF8String]); 57 | } 58 | 59 | self.maxFileSize = newMaxFileSize; 60 | self.maxBackupIndex = newMaxBackupIndex; 61 | } 62 | 63 | return self; 64 | } 65 | 66 | - (id) initWithLayout:(L4Layout *)aLayout fileName:(NSString *)aName 67 | { 68 | return [self initWithLayout:aLayout fileName:aName append:YES]; 69 | } 70 | 71 | - (id) initWithLayout:(L4Layout *)aLayout fileName:(NSString *)aName append:(BOOL) flag 72 | { 73 | self = [super initWithLayout:aLayout fileName:aName append:flag]; 74 | 75 | if (self) { 76 | self.maxFileSize = 1; 77 | self.maxFileSize = kL4RollingFileAppenderDefaultMaxFileSize; 78 | } 79 | 80 | return self; 81 | } 82 | 83 | - (void)rollOver 84 | { 85 | @synchronized(self) { 86 | // if maxBackupIndex is 0, truncate file and create no backups 87 | if (self.maxBackupIndex <= 0) { 88 | [_fileHandle truncateFileAtOffset:0]; 89 | } else { 90 | [self closeFile]; 91 | [self renameLogFile:0]; 92 | [self setupFile]; 93 | } 94 | } 95 | } 96 | 97 | 98 | #pragma mark - Protected methods 99 | 100 | - (void)subAppend:(L4LogEvent*)event 101 | { 102 | @synchronized(self) { 103 | // if the file's size has exceeded maximumFileSize, roll the file over 104 | if ([_fileHandle offsetInFile] >= self.maxFileSize) { 105 | [self rollOver]; 106 | } 107 | 108 | // use the superclass's subAppend 109 | [super subAppend:event]; 110 | } 111 | } 112 | 113 | 114 | #pragma mark - Private methods 115 | 116 | - (void)renameLogFile:(unsigned int)backupIndex 117 | { 118 | NSFileManager* fileManager = nil; 119 | NSString* tempOldFileName = nil; 120 | NSString* tempNewFileName = nil; 121 | NSString* tempPathExtension = nil; 122 | 123 | fileManager = [NSFileManager defaultManager]; 124 | 125 | tempPathExtension = [self.fileName pathExtension]; 126 | 127 | // if we are trying to rename a backup file > maxBackupIndex 128 | if (backupIndex >= [self maxBackupIndex]) { 129 | if ([tempPathExtension length] <= 0) { 130 | tempOldFileName = [NSString stringWithFormat:@"%@.%d", 131 | [[self fileName] stringByDeletingPathExtension], 132 | [self maxBackupIndex]]; 133 | } else { 134 | tempOldFileName = [NSString stringWithFormat:@"%@.%d.%@", 135 | [[[self fileName] stringByDeletingPathExtension] stringByDeletingPathExtension], 136 | [self maxBackupIndex], tempPathExtension]; 137 | } 138 | 139 | // try to delete the oldest backup file 140 | if (![fileManager removeItemAtPath:tempOldFileName error:nil]) { 141 | // if we couldn't delete the file, log an error 142 | [L4LogLog error:[NSString stringWithFormat:@"Unable to delete the file %@", tempOldFileName]]; 143 | } 144 | } else { 145 | // if the backupIndex = 0, we haven't renamed this file before 146 | if (backupIndex == 0) { 147 | tempOldFileName = [self fileName]; 148 | } else { 149 | if ([tempPathExtension length] <= 0) { 150 | // create the old name of the file 151 | tempOldFileName = [NSString stringWithFormat:@"%@.%d", 152 | [[self fileName] stringByDeletingPathExtension], 153 | backupIndex]; 154 | } else { 155 | // create the old name of the file 156 | tempOldFileName = [NSString stringWithFormat:@"%@.%d.%@", 157 | [[[self fileName] stringByDeletingPathExtension] stringByDeletingPathExtension], 158 | backupIndex, 159 | tempPathExtension]; 160 | } 161 | } 162 | 163 | // create the new name of the file 164 | if ([tempPathExtension length] <= 0) { 165 | tempNewFileName = [NSString stringWithFormat:@"%@.%d", 166 | [[self fileName] stringByDeletingPathExtension], 167 | (backupIndex + 1)]; 168 | } else { 169 | tempNewFileName = [NSString stringWithFormat:@"%@.%d.%@", 170 | [[[self fileName] stringByDeletingPathExtension] stringByDeletingPathExtension], 171 | (backupIndex + 1), tempPathExtension]; 172 | } 173 | 174 | // if the new file name already exists, recursively call this method with the new file name's backup index 175 | if ([fileManager fileExistsAtPath:tempNewFileName]) { 176 | [self renameLogFile:(backupIndex + 1)]; 177 | } 178 | 179 | // rename the old file 180 | if (![fileManager moveItemAtPath:tempOldFileName toPath:tempNewFileName error:nil]) { 181 | [L4LogLog error:[NSString stringWithFormat:@"Unable to move file %@ to %@!", tempOldFileName, tempNewFileName]]; 182 | } 183 | } 184 | } 185 | @end 186 | -------------------------------------------------------------------------------- /Log4Cocoa/L4RootLogger.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "L4Logger.h" 3 | 4 | @class L4Level; 5 | 6 | /** 7 | * This class serves as the root of the logging infrastructure; the logger that is typically 8 | * the first created when the logging system is initialized. 9 | */ 10 | @interface L4RootLogger : L4Logger 11 | 12 | /** 13 | * Initialize the new instace with a specified level. 14 | * @param aLevel the level to use at start. 15 | */ 16 | - (id)initWithLevel:(L4Level *)aLevel; 17 | 18 | /** 19 | * Access the level of this logger. 20 | * @return the current level for this logger. 21 | */ 22 | - (L4Level *)effectiveLevel; 23 | 24 | @end 25 | // For copyright & license, see LICENSE. 26 | -------------------------------------------------------------------------------- /Log4Cocoa/L4RootLogger.m: -------------------------------------------------------------------------------- 1 | // For copyright & license, see LICENSE. 2 | 3 | #import "L4RootLogger.h" 4 | #import "L4LogLog.h" 5 | #import "L4Level.h" 6 | 7 | @implementation L4RootLogger 8 | 9 | - (id) initWithLevel:(L4Level *)aLevel 10 | { 11 | self = [super initWithName: @"root"]; 12 | if (self) { 13 | self.level = aLevel; 14 | } 15 | return self; 16 | } 17 | 18 | - (void)setLevel:(L4Level *)aLevel 19 | { 20 | if (aLevel) { 21 | super.level = aLevel; 22 | } else { 23 | [L4LogLog error: @"You have tried to set a null level to root"]; 24 | } 25 | } 26 | 27 | - (L4Level *) effectiveLevel 28 | { 29 | return self.level; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Log4Cocoa/L4SimpleLayout.h: -------------------------------------------------------------------------------- 1 | // For copyright & license, see LICENSE. 2 | 3 | #import 4 | #import "L4Layout.h" 5 | 6 | /** 7 | * A simple layout. 8 | * This class will format an event in the form: 9 | * "%@ - %ldms (%@:%@) %@ - %@" 10 | * the six substitutions are: 11 | * 1) the level of the event. 12 | * 2) the milliseconds since the app was started. 13 | * 3) the name of the file (implementation) the event was loggged from. 14 | * 4) the line number in the file the event was logged from. 15 | * 5) the name of the method the event was logged from. 16 | * 6) the message of the event, if supplied to the log message. 17 | */ 18 | @interface L4SimpleLayout : L4Layout 19 | 20 | 21 | @end 22 | 23 | -------------------------------------------------------------------------------- /Log4Cocoa/L4SimpleLayout.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4SimpleLayout.h" 6 | #import "L4LogEvent.h" 7 | #import "L4Level.h" 8 | 9 | @implementation L4SimpleLayout 10 | 11 | - (NSString *)format:(L4LogEvent *)event 12 | { 13 | return [NSString stringWithFormat:@"%@ - %ldms (%@:%@) %@ - %@", 14 | event.level.stringValue, 15 | event.millisSinceStart, 16 | event.fileName, 17 | event.lineNumber, 18 | event.methodName, 19 | event.renderedMessage]; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Log4Cocoa/L4StringMatchFilter.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "L4Filter.h" 3 | 4 | 5 | /* 6 | * This is a very simple filter based on string matching. 7 | * 8 | * The filter admits two options StringToMatch and AcceptOnMatch. 9 | * 10 | * If there is a match between the value of the StringToMatch option and the message of the L4LoggingEvent, then the 11 | * decide(L4LoggingEvent) method returns L4FilterAccept if the AcceptOnMatch option value is YES/TRUE, if it is 12 | * NO/FALSE, then L4FilterDeny is returned. If there is no match, L4FilterNeutral is returned. 13 | */ 14 | @interface L4StringMatchFilter : L4Filter { 15 | BOOL acceptOnMatch; /**< YES to allow logging on a match, NO to prevent logging on a match. */ 16 | NSString *stringToMatch; /**< The string that the logging events message must contain to match this filter. */ 17 | } 18 | 19 | /** 20 | * Initializes an instance from properties. The properties supported are: 21 | * - AcceptOnMatch: a string that get's converted to a BOOL. YES/NO work well. See the documentation for [NSString 22 | * boolValue] for other options. 23 | * - StringToMatch: the string that the logging events message must contain to match this filter. 24 | * 25 | * @param initProperties the proterties to use. 26 | * @throw L4PropertyMissingException if the StringToMatch property is missing. 27 | */ 28 | - (id) initWithProperties:(L4Properties *)initProperties; 29 | 30 | /** 31 | * Initialze a new instance with the given values. 32 | * This is the default initializer, as the instance should have these two values set. 33 | * 34 | * @param shouldAccept YES to allow logging on a match of levelToMatch, NO to prevent logging on a match. 35 | * @param aString the string that the logging events message must contain to match this filter. 36 | * @throw NSInvalidArgumentException if aString is nil. 37 | */ 38 | - (id) initWithAcceptOnMatch:(BOOL)shouldAccept stringToMatch:(NSString *)aString; 39 | 40 | 41 | /** 42 | * Accessor for the acceptOnMatch property. 43 | */ 44 | - (BOOL) acceptOnMatch; 45 | 46 | /** 47 | * Accessor for the stringToMatch property. 48 | */ 49 | - (NSString *) stringToMatch; 50 | 51 | @end 52 | // For copyright & license, see LICENSE. 53 | -------------------------------------------------------------------------------- /Log4Cocoa/L4StringMatchFilter.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "L4StringMatchFilter.h" 6 | #import "L4LogEvent.h" 7 | #import "L4Properties.h" 8 | 9 | @implementation L4StringMatchFilter 10 | 11 | - (id) initWithAcceptOnMatch:(BOOL)shouldAccept stringToMatch:(NSString *)aString 12 | { 13 | self = [super init]; 14 | if (self) { 15 | acceptOnMatch = shouldAccept; 16 | if (aString == nil || [aString length] == 0) { 17 | self = nil; 18 | [NSException raise:NSInvalidArgumentException format:@"aString is not allowed to be nil."]; 19 | } else { 20 | stringToMatch = aString; 21 | } 22 | } 23 | return self; 24 | } 25 | 26 | -(id) initWithProperties:(L4Properties *) initProperties 27 | { 28 | self = [super initWithProperties:initProperties]; 29 | if (self != nil) { 30 | NSString *acceptIfMatched = [initProperties stringForKey:@"AcceptOnMatch"]; 31 | acceptOnMatch = YES; 32 | 33 | if (acceptIfMatched) { 34 | acceptOnMatch = [acceptIfMatched boolValue]; 35 | } 36 | 37 | stringToMatch = [initProperties stringForKey:@"StringToMatch"]; 38 | if (stringToMatch == nil) { 39 | [NSException raise:L4PropertyMissingException format:@"StringToMatch is a required property."]; 40 | } 41 | } 42 | return self; 43 | } 44 | 45 | 46 | -(BOOL) acceptOnMatch 47 | { 48 | return acceptOnMatch; 49 | } 50 | 51 | -(NSString *) stringToMatch 52 | { 53 | return stringToMatch; 54 | } 55 | 56 | -(L4FilterResult) decide:(L4LogEvent *) logEvent 57 | { 58 | L4FilterResult filterResult = L4FilterNeutral; 59 | if ([logEvent message] != nil && [[logEvent message] rangeOfString:stringToMatch].location != NSNotFound) { 60 | filterResult = (acceptOnMatch ? L4FilterAccept : L4FilterDeny); 61 | } 62 | 63 | return filterResult; 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /Log4Cocoa/L4WriterAppender.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "L4AppenderProtocols.h" 3 | #import "L4AppenderSkeleton.h" 4 | 5 | /** 6 | * An extension of L4Appender that knows how to append by writing to a filehandle. 7 | */ 8 | @interface L4WriterAppender : L4AppenderSkeleton { 9 | NSFileHandle *_fileHandle; /**< The file we are writing to.*/ 10 | } 11 | 12 | /** Flush after write; default is YES. */ 13 | @property BOOL immediateFlush; 14 | 15 | /** The string encoding to use; default is lossy ASCII.*/ 16 | @property NSStringEncoding encoding; 17 | 18 | /** Whether to allow lossy string encoding; default is YES.*/ 19 | @property BOOL lossyEncoding; 20 | 21 | /** 22 | * Default init method. 23 | * @return the new instance. 24 | */ 25 | - (id)init; 26 | 27 | /** 28 | * Creates a new appneder with the provided info. 29 | * @param aLayout the layout to use for this appender. 30 | * @param aFileHandle an exisiting file handle to write to. 31 | * @return the new instance. 32 | */ 33 | - (id)initWithLayout:(L4Layout *)aLayout fileHandle:(NSFileHandle *)aFileHandle; 34 | 35 | /** 36 | * Initializes an instance from properties. The properties supported are: 37 | * - ImmediateFlush: see setImmediateFlush: 38 | * If the values are being set in a file, this is how they could look: 39 | * log4cocoa.appender.A2.ImmediateFlush=false 40 | * @param initProperties the proterties to use. 41 | */ 42 | - (id)initWithProperties:(L4Properties *)initProperties; 43 | 44 | /** 45 | * Reminder: the nesting of calls is: 46 | * 47 | * doAppend() 48 | * - check threshold 49 | * - filter 50 | * - append(); 51 | * - checkEntryConditions(); 52 | * - subAppend(); 53 | */ 54 | - (void)append:(L4LogEvent *)anEvent; 55 | 56 | /** 57 | * Actual writing occurs here. 58 | * 59 | *

Most subclasses of WriterAppender will need to 60 | * override this method. 61 | */ 62 | - (void)subAppend:(L4LogEvent *)anEvent; 63 | 64 | /** 65 | * NOTE --- this method adds a lineBreakChar between log messages. 66 | * So layouts & log messages do not need to add a trailing line break. 67 | */ 68 | - (void)write:(NSString *)theString; 69 | 70 | /** 71 | * Sets the NSFileHandle where the log output will go. 72 | * @param fh The NSFileHandle where you want the log output to go. 73 | */ 74 | - (void)setFileHandle:(NSFileHandle *)fh; 75 | 76 | /** 77 | * This method determines if there is a sense in attempting to append. 78 | * 79 | *

It checks whether there is a set output target and also if 80 | * there is a set layout. If these checks fail, then the boolean 81 | * value false is returned. 82 | */ 83 | - (BOOL) checkEntryConditions; 84 | 85 | /** 86 | * Close the writer. 87 | * This method will close the file handle associated with this appender. Any further 88 | * use of the appender will cause an error. 89 | */ 90 | - (void) closeWriter; 91 | /** 92 | * Close the writer. 93 | * This method will close the file handle associated with this appender. Any further 94 | * use of the appender will cause an error. 95 | */ 96 | - (void) reset; 97 | 98 | /** 99 | * Write the header. 100 | * This method causes the header of the associated layout to be written to the log file. 101 | */ 102 | - (void) writeHeader; 103 | 104 | /** 105 | * Write the footer. 106 | * This method causes the footer of the associated layout to be written to the log file. 107 | */ 108 | - (void) writeFooter; 109 | 110 | @end 111 | 112 | /** 113 | * The methods needed to conform to the L4AppenderCategory catagory. 114 | */ 115 | @interface L4WriterAppender (L4AppenderCategory) 116 | /** 117 | * Causes this writer to close. 118 | */ 119 | - (void) close; 120 | 121 | /** 122 | * Determines if this appender requires a layout. 123 | */ 124 | - (BOOL) requiresLayout; 125 | 126 | @end 127 | // For copyright & license, see LICENSE. 128 | -------------------------------------------------------------------------------- /Log4Cocoa/L4WriterAppender.m: -------------------------------------------------------------------------------- 1 | // For copyright & license, see LICENSE. 2 | 3 | #import "L4WriterAppender.h" 4 | #import "L4Layout.h" 5 | #import "L4LogEvent.h" 6 | #import "L4LogLog.h" 7 | #import "L4Properties.h" 8 | 9 | static NSData *lineBreakChar; 10 | 11 | @implementation L4WriterAppender 12 | 13 | + (void)initialize 14 | { 15 | lineBreakChar = [@"\n" dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]; 16 | } 17 | 18 | - (id) init 19 | { 20 | self = [super init]; 21 | if (self) { 22 | _immediateFlush = YES; 23 | _lossyEncoding = YES; 24 | _encoding = NSUTF8StringEncoding; 25 | } 26 | return self; 27 | } 28 | 29 | - (id) initWithProperties:(L4Properties *) initProperties 30 | { 31 | self = [super initWithProperties:initProperties]; 32 | 33 | if (self) { 34 | BOOL newImmediateFlush = YES; 35 | 36 | // Support for appender.ImmediateFlush in properties configuration file 37 | if ([initProperties stringForKey:@"ImmediateFlush"]) { 38 | NSString *buf = [[initProperties stringForKey:@"ImmediateFlush"] lowercaseString]; 39 | newImmediateFlush = [buf isEqualToString:@"true"]; 40 | } 41 | 42 | self.immediateFlush = newImmediateFlush; 43 | _lossyEncoding = YES; 44 | _encoding = NSUTF8StringEncoding; 45 | } 46 | 47 | return self; 48 | } 49 | 50 | - (id) initWithLayout:(L4Layout *)aLayout fileHandle:(NSFileHandle *)aFileHandle 51 | { 52 | self = [super init]; 53 | if (self) { 54 | self.layout = aLayout; 55 | _fileHandle = aFileHandle; 56 | _immediateFlush = YES; 57 | _lossyEncoding = YES; 58 | _encoding = NSUTF8StringEncoding; 59 | } 60 | return self; 61 | } 62 | 63 | - (void)append:(L4LogEvent *)anEvent 64 | { 65 | @synchronized(self) { 66 | if ([self checkEntryConditions]) { 67 | [self subAppend:anEvent]; 68 | } 69 | } 70 | } 71 | 72 | - (void)subAppend:(L4LogEvent *)anEvent 73 | { 74 | [self write:[self.layout format:anEvent]]; 75 | } 76 | 77 | - (BOOL) checkEntryConditions 78 | { 79 | if (_closed) { 80 | [L4LogLog warn:@"Not allowed to write to a closed appender."]; 81 | return NO; 82 | } 83 | 84 | if (_fileHandle) { 85 | [L4LogLog error:[@"No file handle for output stream set for the appender named:" stringByAppendingString:self.name]]; 86 | return NO; 87 | } 88 | 89 | if (self.layout) { 90 | [L4LogLog error:[@"No layout set for the appender named:" stringByAppendingString:self.name]]; 91 | return NO; 92 | } 93 | 94 | return YES; 95 | } 96 | 97 | - (void) closeWriter 98 | { 99 | @try { 100 | [_fileHandle closeFile]; 101 | } 102 | @catch (NSException *localException) { 103 | [L4LogLog error:[NSString stringWithFormat:@"Could not close file handle:%@\n%@", _fileHandle, localException]]; 104 | } 105 | } 106 | 107 | - (void)setFileHandle:(NSFileHandle*)fh 108 | { 109 | @synchronized(self) { 110 | if (_fileHandle != fh) { 111 | [self closeWriter]; 112 | _fileHandle = fh; 113 | } 114 | } 115 | } 116 | 117 | - (void) reset 118 | { 119 | [self closeWriter]; 120 | } 121 | 122 | - (void) write:(NSString *)string 123 | { 124 | if (string) { 125 | @try { 126 | @synchronized(self) { 127 | // TODO ### -- NEED UNIX EXPERT IS THIS THE BEST WAY ?? 128 | // TODO - ### - NEED TO WORK ON ENCODING ISSUES (& THEN LATER LOCALIZATION) 129 | // 130 | [_fileHandle writeData:[string dataUsingEncoding:_encoding allowLossyConversion:_lossyEncoding]]; 131 | [_fileHandle writeData:lineBreakChar]; 132 | } 133 | } 134 | @catch (NSException *localException) { 135 | [L4LogLog error:[NSString stringWithFormat:@"Appender failed to write string:%@\n%@", string, localException]]; 136 | } 137 | } 138 | } 139 | 140 | - (void) writeHeader 141 | { 142 | [self write:[self.layout header]]; 143 | } 144 | 145 | - (void) writeFooter 146 | { 147 | [self write:[self.layout footer]]; 148 | } 149 | 150 | 151 | #pragma mark - L4AppenderCategory methods 152 | 153 | - (void) close 154 | { 155 | @synchronized(self) { 156 | if (!_closed) { 157 | _closed = YES; 158 | [self writeFooter]; 159 | [self reset]; 160 | } 161 | } 162 | } 163 | 164 | - (BOOL) requiresLayout 165 | { 166 | return YES; 167 | } 168 | 169 | @end 170 | -------------------------------------------------------------------------------- /Log4Cocoa/L4XMLLayout.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | #import "L4Layout.h" 5 | 6 | @interface L4XMLLayout : L4Layout 7 | 8 | + (instancetype)XMLLayout; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Log4Cocoa/L4XMLLayout.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE 3 | */ 4 | 5 | #import "L4XMLLayout.h" 6 | 7 | #import "L4LogEvent.h" 8 | #import "L4Logger.h" 9 | 10 | @implementation L4XMLLayout 11 | 12 | + (instancetype)XMLLayout 13 | { 14 | return [[L4XMLLayout alloc] init]; 15 | } 16 | 17 | - (NSString *)format:(L4LogEvent *) event 18 | { 19 | NSMutableString *formattedEventString = [[NSMutableString alloc] initWithCapacity:1024]; 20 | 21 | [formattedEventString appendFormat:@"\n", 22 | event.logger.name, 23 | event.millisSinceStart, 24 | event.level.stringValue]; 25 | 26 | [formattedEventString appendFormat:@"\t%@:%@\n", event.fileName, event.lineNumber]; 27 | [formattedEventString appendFormat:@"\t%@\n", event.methodName]; 28 | [formattedEventString appendFormat:@"\t%@\n", event.renderedMessage]; 29 | 30 | [formattedEventString appendFormat:@"\n"]; 31 | 32 | return formattedEventString; 33 | } 34 | 35 | - (NSString *)contentType { 36 | return @"application/xml"; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Log4Cocoa/Log4Cocoa-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Log4Cocoa' target in the 'Log4Cocoa' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /Log4Cocoa/Log4Cocoa.h: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import 6 | 7 | #import "L4AppenderAttachable.h" 8 | #import "L4AppenderProtocols.h" 9 | #import "L4AppenderSkeleton.h" 10 | #import "L4BasicConfigurator.h" 11 | #import "L4ConsoleAppender.h" 12 | #import "L4DenyAllFilter.h" 13 | #import "L4FileAppender.h" 14 | #import "L4Filter.h" 15 | #import "L4JSONLayout.h" 16 | #import "L4Layout.h" 17 | #import "L4Level.h" 18 | #import "L4LevelMatchFilter.h" 19 | #import "L4LevelRangeFilter.h" 20 | #import "L4Logger.h" 21 | #import "L4Logging.h" 22 | #import "L4LoggerProtocols.h" 23 | #import "L4LoggerStore.h" 24 | #import "L4LogEvent.h" 25 | #import "L4PatternLayout.h" 26 | #import "L4Properties.h" 27 | #import "L4PropertyConfigurator.h" 28 | #import "L4RollingFileAppender.h" 29 | #import "L4DailyRollingFileAppender.h" 30 | #import "L4RootLogger.h" 31 | #import "L4SimpleLayout.h" 32 | #import "L4StringMatchFilter.h" 33 | #import "L4WriterAppender.h" 34 | #import "L4XMLLayout.h" 35 | #import "Log4CocoaDefines.h" 36 | #import "NSObject+Log4Cocoa.h" 37 | -------------------------------------------------------------------------------- /Log4Cocoa/Log4CocoaDefines.h: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #ifndef _LOG4COCOADEFINES_H 6 | #define _LOG4COCOADEFINES_H 7 | 8 | #import 9 | 10 | 11 | // 12 | // defs for externs 13 | // 14 | 15 | #ifdef __cplusplus 16 | #define LOG4COCOA_EXTERN extern "C" 17 | #define LOG4COCOA_PRIVATE_EXTERN __private_extern__ 18 | #else 19 | #define LOG4COCOA_EXTERN extern 20 | #define LOG4COCOA_PRIVATE_EXTERN __private_extern__ 21 | #endif 22 | 23 | #endif // _LOG4COCOADEFINES_H 24 | 25 | -------------------------------------------------------------------------------- /Log4Cocoa/NSObject+Log4Cocoa.h: -------------------------------------------------------------------------------- 1 | // For copyright & license, see LICENSE. 2 | 3 | #import 4 | @class L4Logger; 5 | 6 | /** 7 | * Convience methods for all NSObject classes. 8 | * This catagory provedes methods to obtain an L4Logger instance from all classes and instances. 9 | * You may want to override these methods in your local base class and provide caching local iVar, 10 | * since these methods result in an NSDictionary lookup each time they are called. Actually it's 11 | * not a bad hit, but in a high volume logging environment, it might make a difference. 12 | * 13 | * Here is an example of what that might look like: 14 | * 15 | * CODE TO ADD TO YOUR BASE CLASS .h file declarations 16 | * \code 17 | * L4Logger *myLoggerIVar; // instance variable 18 | * \endcode 19 | * 20 | * CODE TO ADD TO YOUR BASE CLASS .m file 21 | * \code 22 | * static L4Logger *myLoggerClassVar; // static "class" variable 23 | * 24 | * + (L4Logger *) log 25 | * { 26 | * if( myLoggerClassVar == nil ) { 27 | * myLoggerClassVar = [super l4Logger]; 28 | * } 29 | * 30 | * return myLoggerClassVar; 31 | * } 32 | * 33 | * - (L4Logger *) log 34 | * { 35 | * if( myLoggerIVar == nil ) { 36 | * myLoggerIVar = [super l4Logger]; 37 | * } 38 | * 39 | vreturn myLoggerIVar; 40 | * } 41 | * \endcode 42 | */ 43 | @interface NSObject (Log4Cocoa) 44 | 45 | /** 46 | * Accessor for the L4Logger instance to be used from within class methods. 47 | */ 48 | + (L4Logger *)l4Logger; 49 | 50 | /** 51 | * Accessor for the L4Logger instance to be used from within instance methods. 52 | * The default implementation returns the L4Logger for the class. 53 | */ 54 | - (L4Logger *)l4Logger; 55 | @end 56 | -------------------------------------------------------------------------------- /Log4Cocoa/NSObject+Log4Cocoa.m: -------------------------------------------------------------------------------- 1 | /** 2 | * For copyright & license, see LICENSE. 3 | */ 4 | 5 | #import "NSObject+Log4Cocoa.h" 6 | #import "L4Logger.h" 7 | 8 | 9 | @implementation NSObject (Log4Cocoa) 10 | 11 | + (L4Logger *)l4Logger 12 | { 13 | return [L4Logger loggerForClass:(Class) self]; 14 | } 15 | 16 | - (L4Logger *)l4Logger 17 | { 18 | return [L4Logger loggerForClass:[self class]]; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /README.mdown: -------------------------------------------------------------------------------- 1 | # Log4Cocoa Usage (May 4, 2011) 2 | Read COPYRIGHT 3 | 4 | This document will (hopefully) soon undergo radical redesign & rewriting. Until then, use the forums or e-mail me. 5 | 6 | NOTE: I've removed L4LogManager; I did this because all the code did was class methods, and originally called (just forwarded) from L4Logger. I've also removed the 'simple' logging methods in L4Logger that were there as convenience methods if a user did not want to use the macros. So now, you need to use the macros. If anyone feels this is an issue, let me know. The reason for these removals is that I'm trying to pair down the code (not the functionality) as much as possible. Once proper tests are in place, perhaps I'll look at building some of this back up (if I have requests to do so). 7 | 8 | See below for usage. 9 | 10 | ## I. Usage 11 | To use this framework for logging, in your applications main method, do something liek the following: 12 | 13 |

 14 | // initialize the logging system.
 15 | [[L4Logger rootLogger] setLevel:[L4Level all]];
 16 | [[L4Logger rootLogger] addAppender: [[L4ConsoleAppender alloc] initTarget:YES withLayout: [L4Layout simpleLayout]]];
 17 | L4Logger *theLogger = [L4Logger loggerForClass:[L4FunctionLogger class]];
 18 | [theLogger setLevel:[L4Level info]];
 19 | log4CDebug(@"The logging system has been initialized.");
 20 | 
21 | 22 | Don't forget to: 23 | 24 | `#import ` 25 | 26 | The above code does the following: 27 | * sets the system wide logging level to all (everything gets logged) (this also initializes the logging system). 28 | * sets the default appender to be a console appender using a simple layout. To see the log output, you'll need the console visible (not Console.app, but stdout). 29 | * Get's the logger instance used by methods 30 | * sets the level to info for all logging from all methods (so debug's won't show). 31 | * Logs the string "The logging system has been initialized." 32 | 33 | 34 | To make Log4Cocoa easier, there are several high level macros, as follows (currently nothing is done with the exception; one of the layout classes would need to process it): 35 | 36 | For classes: 37 | 38 |
 39 | log4Debug(message);
 40 | log4Info(message);
 41 | log4Warn(message);
 42 | log4Error(message);
 43 | log4Fatal(message);
 44 | 
 45 | log4DebugWithException(message, e);
 46 | log4InfoWithException(message, e);
 47 | log4WarnWithException(message, e);
 48 | log4ErrorWithException(message, e);
 49 | log4FatalWithException(message, e);
 50 | 
 51 | log4Assert(assertion, message);
 52 | 
53 | 54 | For functions: 55 | 56 |
 57 | log4CDebug(message);
 58 | log4CInfo(message);
 59 | log4CWarn(message);
 60 | log4CError(message);
 61 | log4CFatal(message);
 62 | 
 63 | log4CDebugWithException(message, e);
 64 | log4CInfoWithException(message, e);
 65 | log4CWarnWithException(message, e);
 66 | log4CErrorWithException(message, e);
 67 | log4CFatalWithException(message, e);
 68 | 
 69 | log4CAssert(assertion, message);
 70 | 
71 | 72 | NOTE: when using the xxxWithException macros, the exception goes before any arguments to the message. This is due to the way the macro expands and the way variable length arguments work. Previously, you would wrap the message and it's arguments in parenthesis, followed by the exception. Don't do that now. 73 | 74 | To log a message from an IBAction (as a simple usage example): 75 | 76 |
 77 | - (IBAction)logit:(id) sender
 78 | {
 79 | 	log4Debug(@"Hello %@", [sender description]);
 80 | }
 81 | 
82 | 83 | To log a message with an exception: 84 | 85 |
 86 | - (void) someMethod
 87 | {
 88 | 	NSException *exception = [NSException exceptionWithName:@"Foo" reason:@"Bar" userInfo:nil];
 89 | 	log4DebugWithException(@"Hello %@", exception, @"world!");
 90 | 
 91 | }
 92 | 
93 | 94 | 95 | The macro's for use with functions have the 'C' in them; this seems to be in keeping with what Apple does (for example, with assertions). 96 | 97 | Both versions of the Debug & Info macros expand into methods that are wrapped by an `isEnabled` if statement. 98 | 99 | All of the other levels expand out to their counterparts, but are not enclosed by the `isEnabled` if statement. 100 | 101 | `log4Assert()` & `log4CAssert()` both take a `BOOL` as an assertion expression that must evaluate to `YES`, or else it logs the message as a n error. 102 | 103 | NOTE: none of these macro's include a final semi-colon, so make sure to use one when invoking them. 104 | 105 | The two core logging convenience methods are implemented as categories on `NSObject` (if you want to log off of an `NSProxy`, you'll need to implement these there too ... maybe we'll provide these convenience methods there, but we haven't yet). Since these are categories on `NSObject` we've added the 'l4' prefix to be more paranoid and avoid the remote possiblity of a name collision with someone elses category. 106 | 107 |
108 | @interface NSObject (L4CocoaMethods)
109 | 
110 | + (L4Logger *) l4Logger;
111 | - (L4Logger *) l4Logger;
112 | 
113 | @end
114 | 
115 | 116 | Therefore, `[self l4Logger]` returns a L4Logger instance based on the calling class object. To log a message, without using the above macros, the usage is: 117 | 118 |
119 | [[self l4Logger] l4fatal: @"Crap something fatal happened.  You're screwed.  Game Over."];
120 | 
121 | [[self l4Logger] l4debug: @"Debug info goes here.  La de da.  All the King's horses & all the kings men couldn't put Humpty Dumpty back together again."];
122 | 
123 | 124 | Frankly, I don't know why you wouldn't want to use these macros, but the non-macro versions are still there in case that's what you want or for some reason you can't use the macro versions. 125 | 126 | Since, +logger; & -logger; are implemented as categories on NSObject, you can override them if you don't want the default logger for your class or you want cache the logger. 127 | 128 | For setting level of logging for methods, there is a convenience class L4FunctionLogger that serves; however, keep in mind that any changes to the log level here effect logging from all methods. 129 | 130 | ## II. Embedding Log4Cocoa 131 | 132 | To actually embed Log4Cocoa in your application, you'll need to add a copy phase to your build target. Your application will run as long as Log4Cocoa is installed in your Framework search path but embedding it will make deployment much easier and gdb gets messed up when you use an embedded framework but don't actually embed it. 133 | 134 | From Apple's Documentation: 135 | 136 | http://developer.apple.com/techpubs/macosx/DeveloperTools/ProjectBuilder/ProjectBuilder.help/Contents/Resources/English.lproj/Frameworks/chapter_19_section_3.html 137 | 138 | Copy the Framework Into the Application 139 | 140 | In the application’s target, you need to add a copy-files build phase that copies the framework into the application. 141 | 142 | 143 | 1. Add the framework to the application’s target. See “Adding Files and Frameworks”. If the application’s target and framework’s target are in the same project, you should also make the application’s target dependent on the framework’s target. See “Managing Target Dependencies”. 144 | 145 | 2. In the project window, click the Targets tab and open the application target. 146 | 147 | 3. Click the last build phase under Build Phases, and choose Project > New Build Phase > New Copy Files Build Phase. 148 | 149 | 4. Choose Frameworks from the Where pop-up menu, select the “Copy only when installing” option, and drag the framework to the Files field. 150 | 151 | You can find the framework in the Products group in the project’s Files list. 152 | 153 | 154 | 155 | 156 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/endSly/log4cocoa/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 157 | 158 | --------------------------------------------------------------------------------