Project import
diff --git a/._cocoalumberjack b/._cocoalumberjack
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/._cocoalumberjack
Binary files differ
diff --git a/cocoalumberjack/._.gitignore b/cocoalumberjack/._.gitignore
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/._.gitignore
Binary files differ
diff --git a/cocoalumberjack/._.travis.yml b/cocoalumberjack/._.travis.yml
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/._.travis.yml
Binary files differ
diff --git a/cocoalumberjack/._Benchmarking b/cocoalumberjack/._Benchmarking
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/._Benchmarking
Binary files differ
diff --git a/cocoalumberjack/._CHANGELOG.md b/cocoalumberjack/._CHANGELOG.md
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/._CHANGELOG.md
Binary files differ
diff --git a/cocoalumberjack/._Classes b/cocoalumberjack/._Classes
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/._Classes
Binary files differ
diff --git a/cocoalumberjack/._CocoaLumberjack.podspec b/cocoalumberjack/._CocoaLumberjack.podspec
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/._CocoaLumberjack.podspec
Binary files differ
diff --git a/cocoalumberjack/._Demos b/cocoalumberjack/._Demos
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/._Demos
Binary files differ
diff --git a/cocoalumberjack/._Documentation b/cocoalumberjack/._Documentation
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/._Documentation
Binary files differ
diff --git a/cocoalumberjack/._Framework b/cocoalumberjack/._Framework
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/._Framework
Binary files differ
diff --git a/cocoalumberjack/._LICENSE.txt b/cocoalumberjack/._LICENSE.txt
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/._LICENSE.txt
Binary files differ
diff --git a/cocoalumberjack/._Lumberjack.xcodeproj b/cocoalumberjack/._Lumberjack.xcodeproj
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/._Lumberjack.xcodeproj
Binary files differ
diff --git a/cocoalumberjack/._LumberjackLogo.png b/cocoalumberjack/._LumberjackLogo.png
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/._LumberjackLogo.png
Binary files differ
diff --git a/cocoalumberjack/._README.md b/cocoalumberjack/._README.md
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/._README.md
Binary files differ
diff --git a/cocoalumberjack/._Tests b/cocoalumberjack/._Tests
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/._Tests
Binary files differ
diff --git a/cocoalumberjack/._uncrustify.cfg b/cocoalumberjack/._uncrustify.cfg
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/._uncrustify.cfg
Binary files differ
diff --git a/cocoalumberjack/.gitignore b/cocoalumberjack/.gitignore
new file mode 100644
index 0000000..d1bc424
--- /dev/null
+++ b/cocoalumberjack/.gitignore
@@ -0,0 +1,31 @@
+# Xcode
+#
+build/
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+project.xcworkspace
+!default.xcworkspace
+xcuserdata
+*.xccheckout
+*.moved-aside
+DerivedData
+*.hmap
+*.ipa
+*.xcuserstate
+
+# CocoaPods
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
+#
+Pods/
+Podfile.lock
+LumberjackUser*.h
+.DS_Store
diff --git a/cocoalumberjack/.travis.yml b/cocoalumberjack/.travis.yml
new file mode 100644
index 0000000..ce65b53
--- /dev/null
+++ b/cocoalumberjack/.travis.yml
@@ -0,0 +1,100 @@
+
+osx_image: xcode7.1
+language: objective-c
+
+before_install:
+
+ - export LANG=en_US.UTF-8
+ - env
+ - locale
+ - gem install cocoapods --quiet
+ - pod --version
+ - pod setup --silent
+ - pod repo update --silent
+
+script:
+
+ - pod lib lint
+
+ - xctool -project Lumberjack.xcodeproj -scheme 'CocoaLumberjack'
+ - xctool -project Lumberjack.xcodeproj -scheme 'CocoaLumberjack-iOS' -configuration Release -sdk iphonesimulator build
+ - xctool -project Lumberjack.xcodeproj -scheme 'CocoaLumberjack-watchOS' -configuration Release -sdk watchsimulator build
+ - xctool -project Lumberjack.xcodeproj -scheme 'CocoaLumberjack-tvOS' -configuration Release -sdk appletvsimulator build
+ - xctool -project Lumberjack.xcodeproj -scheme 'FmwkTest'
+ - xctool -project Lumberjack.xcodeproj -scheme 'iOSLibStaticTest' -configuration Release -sdk iphonesimulator build
+ - xctool -project Lumberjack.xcodeproj -scheme 'watchOSSwiftTest' -configuration Release -sdk watchsimulator build
+ - xctool -project Lumberjack.xcodeproj -scheme 'tvOSSwiftTest' -configuration Release -sdk appletvsimulator build
+
+ - pod install --project-directory=Tests
+ - xctool -workspace Framework/Lumberjack.xcworkspace -scheme 'iOS Tests' -sdk iphonesimulator -arch i386 test
+ - xctool -workspace Framework/Lumberjack.xcworkspace -scheme 'OS X Tests' test
+
+ - cd Demos
+ - pod install --project-directory=ContextFilter
+ - xctool -workspace Demos.xcworkspace -scheme 'ContextFilter'
+
+ - pod install --project-directory=CoreDataLogger
+ - xctool -workspace Demos.xcworkspace -scheme 'CoreDataLogger'
+
+ - pod install --project-directory=CustomFormatters
+ - xctool -workspace Demos.xcworkspace -scheme 'CustomFormatters'
+
+ - pod install --project-directory=CustomLogLevels
+ - xctool -workspace Demos.xcworkspace -scheme 'CustomLogLevels'
+
+ - pod install --project-directory=DispatchQueueLogger
+ - xctool -workspace Demos.xcworkspace -scheme 'DispatchQueueLogger'
+
+ - pod install --project-directory=FineGrainedLogging
+ - xctool -workspace Demos.xcworkspace -scheme 'FineGrainedLogging'
+
+ - pod install --project-directory=GlobalLogLevel
+ - xctool -workspace Demos.xcworkspace -scheme 'GlobalLogLevel'
+
+ - pod install --project-directory=LogFileCompressor
+ - xctool -workspace Demos.xcworkspace -scheme 'LogFileCompressor'
+
+ - pod install --project-directory=NonArcTest
+ - xctool -workspace Demos.xcworkspace -scheme 'NonArcTest'
+
+ - pod install --project-directory=OverflowTestMac
+ - xctool -workspace Demos.xcworkspace -scheme 'OverflowTestMac'
+
+ - pod install --project-directory=PerUserLogLevels
+ - xctool -workspace Demos.xcworkspace -scheme 'PerUserLogLevels'
+
+ - pod install --project-directory=RegisteredDynamicLogging/Mobile
+ - xctool -workspace Demos.xcworkspace -scheme 'RegisteredLoggingTest (Mobile)' -configuration Release -sdk iphonesimulator -arch i386 build
+
+ - pod install --project-directory=RegisteredDynamicLogging/Desktop
+ - xctool -workspace Demos.xcworkspace -scheme 'RegisteredLoggingTest (Desktop)'
+
+ - pod install --project-directory=RollingTestMac
+ - xctool -workspace Demos.xcworkspace -scheme 'RollingTestMac'
+
+ - pod install --project-directory=TestXcodeColors/Desktop
+ - xctool -workspace Demos.xcworkspace -scheme 'TestXcodeColors (Desktop)'
+
+ - pod install --project-directory=TestXcodeColors/Mobile
+ - xctool -workspace Demos.xcworkspace -scheme 'TextXcodeColors (Mobile)' -configuration Release -sdk iphonesimulator -arch i386 build
+
+ - pod install --project-directory=WebServerIPhone
+ - xctool -workspace Demos.xcworkspace -scheme 'WebServerIPhone' -configuration Release -sdk iphonesimulator -arch i386 build
+
+ - pod install --project-directory=Benchmark/Desktop
+ - xctool -workspace Demos.xcworkspace -scheme 'BenchmarkMac'
+
+ - pod install --project-directory=Benchmark/Mobile
+ - xctool -workspace Demos.xcworkspace -scheme 'BenchmarkIPhone' -configuration Release -sdk iphonesimulator -arch i386 build
+
+ - pod install --project-directory=SQLiteLogger
+ - xctool -workspace Demos.xcworkspace -scheme 'SQLiteLogger'
+
+ - pod install --project-directory=UniversalApp
+ - xctool -workspace Demos.xcworkspace -scheme 'UniversalApp' -configuration Release -sdk iphonesimulator -arch i386 build
+
+ - pod install --project-directory=CLI
+ - xctool -workspace Demos.xcworkspace -scheme 'CLI'
+
+ - pod install --project-directory=CaptureASL
+ - xctool -workspace Demos.xcworkspace -scheme 'CaptureASL' -configuration Release -sdk iphonesimulator -arch i386 build
diff --git a/cocoalumberjack/Benchmarking/._BaseNSLogging.h b/cocoalumberjack/Benchmarking/._BaseNSLogging.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/._BaseNSLogging.h
Binary files differ
diff --git a/cocoalumberjack/Benchmarking/._BaseNSLogging.m b/cocoalumberjack/Benchmarking/._BaseNSLogging.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/._BaseNSLogging.m
Binary files differ
diff --git a/cocoalumberjack/Benchmarking/._DynamicLogging.h b/cocoalumberjack/Benchmarking/._DynamicLogging.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/._DynamicLogging.h
Binary files differ
diff --git a/cocoalumberjack/Benchmarking/._DynamicLogging.m b/cocoalumberjack/Benchmarking/._DynamicLogging.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/._DynamicLogging.m
Binary files differ
diff --git a/cocoalumberjack/Benchmarking/._PerformanceTesting.h b/cocoalumberjack/Benchmarking/._PerformanceTesting.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/._PerformanceTesting.h
Binary files differ
diff --git a/cocoalumberjack/Benchmarking/._PerformanceTesting.m b/cocoalumberjack/Benchmarking/._PerformanceTesting.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/._PerformanceTesting.m
Binary files differ
diff --git a/cocoalumberjack/Benchmarking/._Results b/cocoalumberjack/Benchmarking/._Results
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/._Results
Binary files differ
diff --git a/cocoalumberjack/Benchmarking/._StaticLogging.h b/cocoalumberjack/Benchmarking/._StaticLogging.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/._StaticLogging.h
Binary files differ
diff --git a/cocoalumberjack/Benchmarking/._StaticLogging.m b/cocoalumberjack/Benchmarking/._StaticLogging.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/._StaticLogging.m
Binary files differ
diff --git a/cocoalumberjack/Benchmarking/BaseNSLogging.h b/cocoalumberjack/Benchmarking/BaseNSLogging.h
new file mode 100644
index 0000000..0d8aa57
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/BaseNSLogging.h
@@ -0,0 +1,12 @@
+#import <Foundation/Foundation.h>
+
+
+@interface BaseNSLogging : NSObject
+
++ (void)speedTest0;
++ (void)speedTest1;
++ (void)speedTest2;
++ (void)speedTest3;
++ (void)speedTest4;
+
+@end
diff --git a/cocoalumberjack/Benchmarking/BaseNSLogging.m b/cocoalumberjack/Benchmarking/BaseNSLogging.m
new file mode 100644
index 0000000..f8af514
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/BaseNSLogging.m
@@ -0,0 +1,92 @@
+#import "BaseNSLogging.h"
+#import "PerformanceTesting.h"
+
+#define DDLogVerbose NSLog
+#define DDLogInfo NSLog
+#define DDLogWarn NSLog
+#define DDLogError NSLog
+
+#define FILENAME @"BaseNSLogging " // Trailing space to match exactly the others in length
+
+
+@implementation BaseNSLogging
+
++ (void)speedTest0
+{
+ // Log statements that will not be executed due to log level
+
+ for (NSUInteger i = 0; i < SPEED_TEST_0_COUNT; i++)
+ {
+ DDLogVerbose(@"%@: SpeedTest0 - %lu", FILENAME, (unsigned long)i);
+ }
+}
+
++ (void)speedTest1
+{
+ // Log statements that will be executed asynchronously
+
+ for (NSUInteger i = 0; i < SPEED_TEST_1_COUNT; i++)
+ {
+ DDLogWarn(@"%@: SpeedTest1 - %lu", FILENAME, (unsigned long)i);
+ }
+}
+
++ (void)speedTest2
+{
+ // Log statements that will be executed synchronously
+
+ for (NSUInteger i = 0; i < SPEED_TEST_2_COUNT; i++)
+ {
+ DDLogError(@"%@: SpeedTest2 - %lu", FILENAME, (unsigned long)i);
+ }
+}
+
++ (void)speedTest3
+{
+ // Even Spread:
+ //
+ // 25% - Not executed due to log level
+ // 50% - Executed asynchronously
+ // 25% - Executed synchronously
+
+ for (NSUInteger i = 0; i < SPEED_TEST_3_COUNT; i++)
+ {
+ DDLogError(@"%@: SpeedTest3A - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_3_COUNT; i++)
+ {
+ DDLogWarn(@"%@: SpeedTest3B - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_3_COUNT; i++)
+ {
+ DDLogInfo(@"%@: SpeedTest3C - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_3_COUNT; i++)
+ {
+ DDLogVerbose(@"%@: SpeedTest3D - %lu", FILENAME, (unsigned long)i);
+ }
+}
+
++ (void)speedTest4
+{
+ // Custom Spread
+
+ for (NSUInteger i = 0; i < SPEED_TEST_4_ERROR_COUNT; i++)
+ {
+ DDLogError(@"%@: SpeedTest4A - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_4_WARN_COUNT; i++)
+ {
+ DDLogWarn(@"%@: SpeedTest4B - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_4_INFO_COUNT; i++)
+ {
+ DDLogInfo(@"%@: SpeedTest4C - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_4_VERBOSE_COUNT; i++)
+ {
+ DDLogVerbose(@"%@: SpeedTest4D - %lu", FILENAME, (unsigned long)i);
+ }
+}
+
+@end
diff --git a/cocoalumberjack/Benchmarking/DynamicLogging.h b/cocoalumberjack/Benchmarking/DynamicLogging.h
new file mode 100644
index 0000000..e39e5de
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/DynamicLogging.h
@@ -0,0 +1,12 @@
+#import <Foundation/Foundation.h>
+
+
+@interface DynamicLogging : NSObject
+
++ (void)speedTest0;
++ (void)speedTest1;
++ (void)speedTest2;
++ (void)speedTest3;
++ (void)speedTest4;
+
+@end
diff --git a/cocoalumberjack/Benchmarking/DynamicLogging.m b/cocoalumberjack/Benchmarking/DynamicLogging.m
new file mode 100644
index 0000000..0b10a60
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/DynamicLogging.m
@@ -0,0 +1,101 @@
+#import "DynamicLogging.h"
+#import "PerformanceTesting.h"
+#import "DDLogMacros.h"
+
+#define FILENAME @"DynamicLogging"
+
+// Debug levels: off, error, warn, info, verbose
+static DDLogLevel ddLogLevel = DDLogLevelWarning; // NOT CONST
+
+
+@implementation DynamicLogging
+
++ (DDLogLevel)ddLogLevel
+{
+ return ddLogLevel;
+}
+
++ (void)ddSetLogLevel:(DDLogLevel)logLevel
+{
+ ddLogLevel = logLevel;
+}
+
++ (void)speedTest0
+{
+ // Log statements that will not be executed due to log level
+
+ for (NSUInteger i = 0; i < SPEED_TEST_0_COUNT; i++)
+ {
+ DDLogVerbose(@"%@: SpeedTest0 - %lu", FILENAME, (unsigned long)i);
+ }
+}
+
++ (void)speedTest1
+{
+ // Log statements that will be executed asynchronously
+
+ for (NSUInteger i = 0; i < SPEED_TEST_1_COUNT; i++)
+ {
+ DDLogWarn(@"%@: SpeedTest1 - %lu", FILENAME, (unsigned long)i);
+ }
+}
+
++ (void)speedTest2
+{
+ // Log statements that will be executed synchronously
+
+ for (NSUInteger i = 0; i < SPEED_TEST_2_COUNT; i++)
+ {
+ DDLogError(@"%@: SpeedTest2 - %lu", FILENAME, (unsigned long)i);
+ }
+}
+
++ (void)speedTest3
+{
+ // Even Spread:
+ //
+ // 25% - Not executed due to log level
+ // 50% - Executed asynchronously
+ // 25% - Executed synchronously
+
+ for (NSUInteger i = 0; i < SPEED_TEST_3_COUNT; i++)
+ {
+ DDLogError(@"%@: SpeedTest3A - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_3_COUNT; i++)
+ {
+ DDLogWarn(@"%@: SpeedTest3B - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_3_COUNT; i++)
+ {
+ DDLogInfo(@"%@: SpeedTest3C - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_3_COUNT; i++)
+ {
+ DDLogVerbose(@"%@: SpeedTest3D - %lu", FILENAME, (unsigned long)i);
+ }
+}
+
++ (void)speedTest4
+{
+ // Custom Spread
+
+ for (NSUInteger i = 0; i < SPEED_TEST_4_ERROR_COUNT; i++)
+ {
+ DDLogError(@"%@: SpeedTest4A - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_4_WARN_COUNT; i++)
+ {
+ DDLogWarn(@"%@: SpeedTest4B - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_4_INFO_COUNT; i++)
+ {
+ DDLogInfo(@"%@: SpeedTest4C - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_4_VERBOSE_COUNT; i++)
+ {
+ DDLogVerbose(@"%@: SpeedTest4D - %lu", FILENAME, (unsigned long)i);
+ }
+}
+
+@end
diff --git a/cocoalumberjack/Benchmarking/PerformanceTesting.h b/cocoalumberjack/Benchmarking/PerformanceTesting.h
new file mode 100644
index 0000000..1851ffa
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/PerformanceTesting.h
@@ -0,0 +1,19 @@
+#import <Foundation/Foundation.h>
+
+#define SPEED_TEST_0_COUNT 1000 // Total log statements
+#define SPEED_TEST_1_COUNT 1000 // Total log statements
+#define SPEED_TEST_2_COUNT 1000 // Total log statements
+#define SPEED_TEST_3_COUNT 250 // Per log level (multiply by 4 to get total)
+
+#define SPEED_TEST_4_VERBOSE_COUNT 900
+#define SPEED_TEST_4_INFO_COUNT 000
+#define SPEED_TEST_4_WARN_COUNT 000
+#define SPEED_TEST_4_ERROR_COUNT 100
+
+// Further documentation on these tests may be found in the implementation file.
+
+@interface PerformanceTesting : NSObject
+
++ (void)startPerformanceTests;
+
+@end
diff --git a/cocoalumberjack/Benchmarking/PerformanceTesting.m b/cocoalumberjack/Benchmarking/PerformanceTesting.m
new file mode 100644
index 0000000..a6c5b59
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/PerformanceTesting.m
@@ -0,0 +1,416 @@
+#import "PerformanceTesting.h"
+#import "DDLog.h"
+#import "DDASLLogger.h"
+#import "DDTTYLogger.h"
+#import "DDFileLogger.h"
+
+
+#import "BaseNSLogging.h"
+#import "StaticLogging.h"
+#import "DynamicLogging.h"
+
+// Define the number of times each test is performed.
+// Due to various factors, the execution time of each test run may vary quite a bit.
+// Each test should be executed several times in order to arrive at a stable average.
+#define NUMBER_OF_RUNS 20
+
+/**
+ * The idea behind the benchmark tests is simple:
+ * How does the logging framework compare to basic NSLog statements?
+ *
+ * However, due to the complexity of the logging framework and its various configuration options,
+ * it is more complicated than a single test. Thus the testing is broken up as follows:
+ *
+ * - 3 Suites, each representing a different configuration of the logging framework
+ * - 5 Tests, run within each suite.
+ *
+ * The suites are described below in the configureLoggingForSuiteX methods.
+ * The tests are described in the various logging files, such as StaticLogging or DynamicLogging.
+ * Notice that these file are almost exactly the same.
+ *
+ * BaseNSLogging defines the log methods to use NSLog (the base we are comparing against).
+ * StaticLogging uses a 'const' log level, meaning the compiler will prune log statements (in release mode).
+ * DynamicLogging use a non-const log level, meaning each log statement will incur an integer comparison penalty.
+**/
+
+@implementation PerformanceTesting
+
+static NSTimeInterval base[5][3]; // [test][min,avg,max]
+
+static NSTimeInterval fmwk[3][2][5][3]; // [suite][file][test][min,avg,max]
+
+static DDFileLogger *fileLogger = nil;
+
++ (void)initialize
+{
+ bzero(&base, sizeof(base));
+ bzero(&fmwk, sizeof(fmwk));
+}
+
++ (DDFileLogger *)fileLogger
+{
+ if (fileLogger == nil)
+ {
+ fileLogger = [[DDFileLogger alloc] init];
+
+ fileLogger.maximumFileSize = (1024 * 1024 * 1); // 1 MB
+ fileLogger.rollingFrequency = (60 * 60 * 24); // 24 Hours
+
+ fileLogger.logFileManager.maximumNumberOfLogFiles = 4;
+ }
+
+ return fileLogger;
+}
+
+/**
+ * Suite 1 - Logging to Console only.
+**/
++ (void)configureLoggingForSuite1
+{
+ [DDLog removeAllLoggers];
+
+ [DDLog addLogger:[DDASLLogger sharedInstance]];
+ [DDLog addLogger:[DDTTYLogger sharedInstance]];
+}
+
+/**
+ * Suite 2 - Logging to File only.
+ *
+ * We attempt to configure the logging so it will be forced to roll the log files during the test.
+ * Rolling the log files requires creating and opening a new file.
+ * This could be a performance hit, so we want our benchmark to take this into account.
+**/
++ (void)configureLoggingForSuite2
+{
+ [DDLog removeAllLoggers];
+
+ [DDLog addLogger:[self fileLogger]];
+}
+
+/**
+ * Suite 3 - Logging to Console & File.
+**/
++ (void)configureLoggingForSuite3
+{
+ [DDLog removeAllLoggers];
+
+ [DDLog addLogger:[DDASLLogger sharedInstance]];
+ [DDLog addLogger:[DDTTYLogger sharedInstance]];
+ [DDLog addLogger:[self fileLogger]];
+}
+
++ (void)executeTestsWithBase:(BOOL)exeBase framework:(BOOL)exeFramework frameworkSuite:(int)suiteNum
+{
+ if (!exeBase && !exeFramework) return;
+
+ int sn = suiteNum - 1; // Zero-indexed for array
+
+ int i, j, k;
+
+ int start = exeBase ? 0 : 1;
+ int finish = exeFramework ? 3 : 1;
+
+ for (i = start; i < finish; i++)
+ {
+ Class class;
+ switch (i)
+ {
+ case 0 : class = [BaseNSLogging class]; break;
+ case 1 : class = [StaticLogging class]; break;
+ default : class = [DynamicLogging class]; break;
+ }
+
+ for (j = 0; j < 5; j++)
+ {
+ NSTimeInterval min = DBL_MAX;
+ NSTimeInterval max = DBL_MIN;
+
+ NSTimeInterval total = 0.0;
+
+ for (k = 0; k < NUMBER_OF_RUNS; k++)
+ {
+ @autoreleasepool {
+
+ NSDate *start = [NSDate date];
+
+ switch (j)
+ {
+ case 0 : [class performSelector:@selector(speedTest0)]; break;
+ case 1 : [class performSelector:@selector(speedTest1)]; break;
+ case 2 : [class performSelector:@selector(speedTest2)]; break;
+ case 3 : [class performSelector:@selector(speedTest3)]; break;
+ default : [class performSelector:@selector(speedTest4)]; break;
+ }
+
+ NSTimeInterval result = [start timeIntervalSinceNow] * -1.0;
+
+ min = MIN(min, result);
+ max = MAX(max, result);
+
+ total += result;
+
+ }
+ [DDLog flushLog];
+ }
+
+ if (i == 0)
+ {
+ // Base
+ base[j][0] = min;
+ base[j][1] = total / (double)NUMBER_OF_RUNS;
+ base[j][2] = max;
+ }
+ else
+ {
+ // Framework
+ fmwk[sn][i-1][j][0] = min;
+ fmwk[sn][i-1][j][1] = total / (double)NUMBER_OF_RUNS;
+ fmwk[sn][i-1][j][2] = max;
+ }
+ }
+ }
+}
+
++ (NSString *)printableResultsForSuite:(int)suiteNum
+{
+ int sn = suiteNum - 1; // Zero-indexed for array
+
+ NSMutableString *str = [NSMutableString stringWithCapacity:2000];
+
+ [str appendFormat:@"Results are given as [min][avg][max] calculated over the course of %i runs.", NUMBER_OF_RUNS];
+ [str appendString:@"\n\n"];
+
+ [str appendString:@"Test 0:\n"];
+ [str appendFormat:@"Execute %i log statements.\n", SPEED_TEST_0_COUNT];
+ [str appendString:@"The log statement is above the log level threshold, and will not execute.\n"];
+ [str appendString:@"The StaticLogging class will compile it out (in release mode).\n"];
+ [str appendString:@"The DynamicLogging class will require a single integer comparison.\n"];
+ [str appendString:@"\n"];
+ [str appendFormat:@"BaseNSLogging :[%.4f][%.4f][%.4f]\n", base[0][0], base[0][1], base[0][2]];
+ [str appendFormat:@"StaticLogging :[%.4f][%.4f][%.4f]\n", fmwk[sn][0][0][0], fmwk[sn][0][0][1], fmwk[sn][0][0][2]];
+ [str appendFormat:@"DynamicLogging:[%.4f][%.4f][%.4f]\n", fmwk[sn][1][0][0], fmwk[sn][1][0][1], fmwk[sn][1][0][2]];
+ [str appendString:@"\n\n\n"];
+
+ [str appendString:@"Test 1:\n"];
+ [str appendFormat:@"Execute %i log statements.\n", SPEED_TEST_1_COUNT];
+ [str appendString:@"The log statement is at or below the log level threshold, and will execute.\n"];
+ [str appendString:@"The logging framework will execute the statements Asynchronously.\n"];
+ [str appendString:@"\n"];
+ [str appendFormat:@"BaseNSLogging :[%.4f][%.4f][%.4f]\n", base[1][0], base[1][1], base[1][2]];
+ [str appendFormat:@"StaticLogging :[%.4f][%.4f][%.4f]\n", fmwk[sn][0][1][0], fmwk[sn][0][1][1], fmwk[sn][0][1][2]];
+ [str appendFormat:@"DynamicLogging:[%.4f][%.4f][%.4f]\n", fmwk[sn][1][1][0], fmwk[sn][1][1][1], fmwk[sn][1][1][2]];
+ [str appendString:@"\n\n\n"];
+
+ [str appendString:@"Test 2:\n"];
+ [str appendFormat:@"Execute %i log statements.\n", SPEED_TEST_2_COUNT];
+ [str appendString:@"The log statement is at or below the log level threshold, and will execute.\n"];
+ [str appendString:@"The logging framework will execute the statements Synchronously.\n"];
+ [str appendString:@"\n"];
+ [str appendFormat:@"BaseNSLogging :[%.4f][%.4f][%.4f]\n", base[2][0], base[2][1], base[2][2]];
+ [str appendFormat:@"StaticLogging :[%.4f][%.4f][%.4f]\n", fmwk[sn][0][2][0], fmwk[sn][0][2][1], fmwk[sn][0][2][2]];
+ [str appendFormat:@"DynamicLogging:[%.4f][%.4f][%.4f]\n", fmwk[sn][1][2][0], fmwk[sn][1][2][1], fmwk[sn][1][2][2]];
+ [str appendString:@"\n\n\n"];
+
+ [str appendString:@"Test 3:"];
+ [str appendFormat:@"Execute %i log statements per level.\n", SPEED_TEST_3_COUNT];
+ [str appendString:@"This is designed to mimic what might happen in a regular application.\n"];
+ [str appendString:@"25% will be above log level threshold and will be filtered out.\n"];
+ [str appendString:@"50% will execute Asynchronously.\n"];
+ [str appendString:@"25% will execute Synchronously.\n"];
+ [str appendString:@"\n"];
+ [str appendFormat:@"BaseNSLogging :[%.4f][%.4f][%.4f]\n", base[3][0], base[3][1], base[3][2]];
+ [str appendFormat:@"StaticLogging :[%.4f][%.4f][%.4f]\n", fmwk[sn][0][3][0], fmwk[sn][0][3][1], fmwk[sn][0][3][2]];
+ [str appendFormat:@"DynamicLogging:[%.4f][%.4f][%.4f]\n", fmwk[sn][1][3][0], fmwk[sn][1][3][1], fmwk[sn][1][3][2]];
+ [str appendString:@"\n\n\n"];
+
+ float total = 0.0F;
+ total += SPEED_TEST_4_VERBOSE_COUNT;
+ total += SPEED_TEST_4_INFO_COUNT;
+ total += SPEED_TEST_4_WARN_COUNT;
+ total += SPEED_TEST_4_ERROR_COUNT;
+
+ float verbose = (float)SPEED_TEST_4_VERBOSE_COUNT / total * 100.0F;
+ float info = (float)SPEED_TEST_4_INFO_COUNT / total * 100.0F;
+ float warn = (float)SPEED_TEST_4_WARN_COUNT / total * 100.0F;
+ float error = (float)SPEED_TEST_4_ERROR_COUNT / total * 100.0F;
+
+ [str appendString:@"Test 4:\n"];
+ [str appendString:@"Similar to test 3, designed to mimic a real application\n"];
+ [str appendFormat:@"Execute %i log statements in total.\n", (int)total];
+ [str appendFormat:@"%04.1f%% will be above log level threshold and will be filtered out.\n", verbose];
+ [str appendFormat:@"%04.1f%% will execute Asynchronously.\n", (info + warn)];
+ [str appendFormat:@"%04.1f%% will execute Synchronously.\n", error];
+ [str appendString:@"\n"];
+ [str appendFormat:@"BaseNSLogging :[%.4f][%.4f][%.4f]\n", base[4][0], base[4][1], base[4][2]];
+ [str appendFormat:@"StaticLogging :[%.4f][%.4f][%.4f]\n", fmwk[sn][0][4][0], fmwk[sn][0][4][1], fmwk[sn][0][4][2]];
+ [str appendFormat:@"DynamicLogging:[%.4f][%.4f][%.4f]\n", fmwk[sn][1][4][0], fmwk[sn][1][4][1], fmwk[sn][1][4][2]];
+ [str appendString:@"\n\n\n"];
+
+ return str;
+}
+
++ (NSString *)csvResults
+{
+ NSMutableString *str = [NSMutableString stringWithCapacity:1000];
+
+ // What are we trying to do here?
+ //
+ // What we ultimately want is to compare the performance of the framework against the baseline.
+ // This means we want to see the performance of the baseline for test 1,
+ // and then right next to it we want to see the performance of the framework with each various configuration.
+ //
+ // So we want it to kinda look like this for Test 1:
+ //
+ // Base, [min], [avg], [max]
+ // Suite 1 - Static, [min], [avg], [max]
+ // Suite 1 - Dynamic, [min], [avg], [max]
+ // Suite 2 - Static, [min], [avg], [max]
+ // Suite 2 - Dynamic, [min], [avg], [max]
+ // Suite 3 - Static, [min], [avg], [max]
+ // Suite 3 - Dynamic, [min], [avg], [max]
+ //
+ // This will import into Excel just fine.
+ // However, I couldn't get Excel to make a decent looking graph with the data.
+ // Perhaps I'm just not familiar enough with Excel.
+ // But I was able to download OmniGraphSketcher,
+ // and figure out how to create an awesome looking graph in less than 15 minutes.
+ // And thus OmniGraphSketcher wins for me.
+ // The only catch is that it wants to import the data with numbers instead of names.
+ // So I need to convert the output to look like this:
+ //
+ // 0, [min], [avg], [max]
+ // 1, [min], [avg], [max]
+ // 2, [min], [avg], [max]
+ // 3, [min], [avg], [max]
+ // 4, [min], [avg], [max]
+ // 5, [min], [avg], [max]
+ // 6, [min], [avg], [max]
+ //
+ // I can then import the data into OmniGraphSketcher, and rename the X-axis points.
+
+ // static NSTimeInterval base[5][3]; // [test][min,avg,max]
+ //
+ // static NSTimeInterval fmwk[3][2][5][3]; // [suite][file][test][min,avg,max]
+
+ int row = 0;
+ int suite, file, test;
+
+ for (test = 0; test < 5; test++)
+ {
+ [str appendFormat:@"%i, %.4f, %.4f, %.4f\n", row++, base[test][0], base[test][1], base[test][2]];
+
+ for (suite = 0; suite < 3; suite++)
+ {
+ for (file = 0; file < 2; file++)
+ {
+ [str appendFormat:@"%i, %.4f, %.4f, %.4f\n", row++,
+ fmwk[suite][file][test][0], fmwk[suite][file][test][1], fmwk[suite][file][test][2]];
+ }
+ }
+
+ row += 3;
+ }
+
+ return str;
+}
+
++ (void)startPerformanceTests
+{
+ BOOL runBase = YES;
+ BOOL runSuite1 = YES;
+ BOOL runSuite2 = YES;
+ BOOL runSuite3 = YES;
+
+ if (!runBase && !runSuite1 && !runSuite2 && !runSuite3)
+ {
+ // Nothing to do, all suites disabled
+ return;
+ }
+
+ NSLog(@"Preparing to start performance tests...");
+ NSLog(@"The results will be printed nicely when all logging has completed.\n\n");
+
+ [NSThread sleepForTimeInterval:3.0];
+
+ if (runBase)
+ {
+ [self executeTestsWithBase:YES framework:NO frameworkSuite:0];
+ }
+
+ NSString *printableResults1 = nil;
+ NSString *printableResults2 = nil;
+ NSString *printableResults3 = nil;
+
+ if (runSuite1)
+ {
+ [self configureLoggingForSuite1];
+ [self executeTestsWithBase:NO framework:YES frameworkSuite:1];
+
+ printableResults1 = [self printableResultsForSuite:1];
+
+ NSLog(@"\n\n\n\n");
+ }
+ if (runSuite2)
+ {
+ [self configureLoggingForSuite2];
+ [self executeTestsWithBase:NO framework:YES frameworkSuite:2];
+
+ printableResults2 = [self printableResultsForSuite:2];
+
+ NSLog(@"\n\n\n\n");
+ }
+ if (runSuite3)
+ {
+ [self configureLoggingForSuite3];
+ [self executeTestsWithBase:NO framework:YES frameworkSuite:3];
+
+ printableResults3 = [self printableResultsForSuite:3];
+
+ NSLog(@"\n\n\n\n");
+ }
+
+ if (runSuite1)
+ {
+ NSLog(@"======================================================================");
+ NSLog(@"Benchmark Suite 1:");
+ NSLog(@"Logging framework configured to log to console only.");
+ NSLog(@"\n\n%@", printableResults1);
+ NSLog(@"======================================================================");
+ }
+ if (runSuite2)
+ {
+ NSLog(@"======================================================================");
+ NSLog(@"Benchmark Suite 2:");
+ NSLog(@"Logging framework configured to log to file only.");
+ NSLog(@"\n\n%@", printableResults2);
+ NSLog(@"======================================================================");
+ }
+ if (runSuite3)
+ {
+ NSLog(@"======================================================================");
+ NSLog(@"Benchmark Suite 3:");
+ NSLog(@"Logging framework configured to log to console & file.");
+ NSLog(@"\n\n%@", printableResults3);
+ NSLog(@"======================================================================");
+ }
+
+#if TARGET_OS_IPHONE
+ NSString *csvResultsPath = [@"~/Documents/LumberjackBenchmark.csv" stringByExpandingTildeInPath];
+#else
+ NSString *csvResultsPath = [@"~/Desktop/LumberjackBenchmark.csv" stringByExpandingTildeInPath];
+#endif
+
+ if (![[NSFileManager defaultManager] fileExistsAtPath:csvResultsPath])
+ {
+ [[NSFileManager defaultManager] createFileAtPath:csvResultsPath contents:nil attributes:nil];
+ }
+
+ NSFileHandle *csvResultsFile = [NSFileHandle fileHandleForWritingAtPath:csvResultsPath];
+
+ NSString *csvRsults = [self csvResults];
+ [csvResultsFile writeData:[csvRsults dataUsingEncoding:NSUTF8StringEncoding]];
+
+ NSLog(@"CSV results file written to:\n%@", csvResultsPath);
+}
+
+@end
diff --git a/cocoalumberjack/Benchmarking/Results/._Benchmark PowerMac.csv b/cocoalumberjack/Benchmarking/Results/._Benchmark PowerMac.csv
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/Results/._Benchmark PowerMac.csv
Binary files differ
diff --git a/cocoalumberjack/Benchmarking/Results/._Benchmark iMac.csv b/cocoalumberjack/Benchmarking/Results/._Benchmark iMac.csv
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/Results/._Benchmark iMac.csv
Binary files differ
diff --git a/cocoalumberjack/Benchmarking/Results/._Benchmark iPad.csv b/cocoalumberjack/Benchmarking/Results/._Benchmark iPad.csv
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/Results/._Benchmark iPad.csv
Binary files differ
diff --git a/cocoalumberjack/Benchmarking/Results/._Benchmark iPhone 3GS.csv b/cocoalumberjack/Benchmarking/Results/._Benchmark iPhone 3GS.csv
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/Results/._Benchmark iPhone 3GS.csv
Binary files differ
diff --git a/cocoalumberjack/Benchmarking/Results/._Benchmark iPodTouch3 GCD.csv b/cocoalumberjack/Benchmarking/Results/._Benchmark iPodTouch3 GCD.csv
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/Results/._Benchmark iPodTouch3 GCD.csv
Binary files differ
diff --git "a/cocoalumberjack/Benchmarking/Results/._Lumberjack Benchmark \050PowerMac\051.ograph" "b/cocoalumberjack/Benchmarking/Results/._Lumberjack Benchmark \050PowerMac\051.ograph"
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ "b/cocoalumberjack/Benchmarking/Results/._Lumberjack Benchmark \050PowerMac\051.ograph"
Binary files differ
diff --git "a/cocoalumberjack/Benchmarking/Results/._Lumberjack Benchmark \050iMac\051.ograph" "b/cocoalumberjack/Benchmarking/Results/._Lumberjack Benchmark \050iMac\051.ograph"
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ "b/cocoalumberjack/Benchmarking/Results/._Lumberjack Benchmark \050iMac\051.ograph"
Binary files differ
diff --git "a/cocoalumberjack/Benchmarking/Results/._Lumberjack Benchmark \050iPad\051.ograph" "b/cocoalumberjack/Benchmarking/Results/._Lumberjack Benchmark \050iPad\051.ograph"
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ "b/cocoalumberjack/Benchmarking/Results/._Lumberjack Benchmark \050iPad\051.ograph"
Binary files differ
diff --git "a/cocoalumberjack/Benchmarking/Results/._Lumberjack Benchmark \050iPhone 3GS\051.ograph" "b/cocoalumberjack/Benchmarking/Results/._Lumberjack Benchmark \050iPhone 3GS\051.ograph"
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ "b/cocoalumberjack/Benchmarking/Results/._Lumberjack Benchmark \050iPhone 3GS\051.ograph"
Binary files differ
diff --git a/cocoalumberjack/Benchmarking/Results/Benchmark PowerMac.csv b/cocoalumberjack/Benchmarking/Results/Benchmark PowerMac.csv
new file mode 100644
index 0000000..b560dff
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/Results/Benchmark PowerMac.csv
@@ -0,0 +1,35 @@
+0, 0.2724, 0.2816, 0.3198
+1, 0.0000, 0.0000, 0.0000
+2, 0.0000, 0.0000, 0.0000
+3, 0.0000, 0.0000, 0.0000
+4, 0.0000, 0.0000, 0.0000
+5, 0.0000, 0.0000, 0.0000
+6, 0.0000, 0.0000, 0.0000
+10, 0.2710, 0.2757, 0.2826
+11, 0.0032, 0.0035, 0.0043
+12, 0.0032, 0.0036, 0.0066
+13, 0.0032, 0.0032, 0.0033
+14, 0.0032, 0.0032, 0.0033
+15, 0.0032, 0.0035, 0.0054
+16, 0.0032, 0.0037, 0.0065
+20, 0.2715, 0.2804, 0.3601
+21, 0.0996, 0.1037, 0.1090
+22, 0.0984, 0.1042, 0.1265
+23, 0.0296, 0.0311, 0.0370
+24, 0.0294, 0.0301, 0.0314
+25, 0.1118, 0.1523, 0.8192
+26, 0.1089, 0.1138, 0.1204
+30, 0.2720, 0.2760, 0.2842
+31, 0.0251, 0.0269, 0.0287
+32, 0.0260, 0.0273, 0.0298
+33, 0.0080, 0.0084, 0.0089
+34, 0.0079, 0.0084, 0.0091
+35, 0.0278, 0.0298, 0.0322
+36, 0.0277, 0.0299, 0.0319
+40, 0.2707, 0.2797, 0.3354
+41, 0.0095, 0.0103, 0.0112
+42, 0.0096, 0.0104, 0.0113
+43, 0.0028, 0.0030, 0.0034
+44, 0.0028, 0.0029, 0.0031
+45, 0.0098, 0.0112, 0.0120
+46, 0.0104, 0.0114, 0.0122
diff --git a/cocoalumberjack/Benchmarking/Results/Benchmark iMac.csv b/cocoalumberjack/Benchmarking/Results/Benchmark iMac.csv
new file mode 100644
index 0000000..c749ef1
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/Results/Benchmark iMac.csv
@@ -0,0 +1,35 @@
+0, 0.6169, 0.6700, 0.7403
+1, 0.0000, 0.0000, 0.0000
+2, 0.0000, 0.0000, 0.0000
+3, 0.0000, 0.0000, 0.0000
+4, 0.0000, 0.0000, 0.0000
+5, 0.0000, 0.0000, 0.0000
+6, 0.0000, 0.0000, 0.0000
+10, 0.6106, 0.6640, 0.7329
+11, 0.0184, 0.0268, 0.0466
+12, 0.0185, 0.0243, 0.0477
+13, 0.0183, 0.0213, 0.0234
+14, 0.0213, 0.0223, 0.0246
+15, 0.0182, 0.0231, 0.0426
+16, 0.0182, 0.0207, 0.0286
+20, 0.6167, 0.6651, 0.7365
+21, 0.2614, 0.3090, 0.3966
+22, 0.2672, 0.3121, 0.3805
+23, 0.0541, 0.0675, 0.0834
+24, 0.0541, 0.0576, 0.0824
+25, 0.2795, 0.3334, 0.4408
+26, 0.2847, 0.3438, 0.4951
+30, 0.6077, 0.6560, 0.7029
+31, 0.0676, 0.0997, 0.2275
+32, 0.0723, 0.0945, 0.1568
+33, 0.0188, 0.0191, 0.0203
+34, 0.0187, 0.0192, 0.0233
+35, 0.0723, 0.0886, 0.1579
+36, 0.0774, 0.0934, 0.1095
+40, 0.6101, 0.6597, 0.7372
+41, 0.0189, 0.0308, 0.0484
+42, 0.0237, 0.0322, 0.0468
+43, 0.0053, 0.0054, 0.0056
+44, 0.0053, 0.0055, 0.0057
+45, 0.0233, 0.0347, 0.0780
+46, 0.0223, 0.0316, 0.0559
diff --git a/cocoalumberjack/Benchmarking/Results/Benchmark iPad.csv b/cocoalumberjack/Benchmarking/Results/Benchmark iPad.csv
new file mode 100644
index 0000000..9760c6d
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/Results/Benchmark iPad.csv
@@ -0,0 +1,35 @@
+0, 2.4968, 2.5606, 2.7525
+1, 0.0001, 0.0001, 0.0001
+2, 0.0000, 0.0000, 0.0000
+3, 0.0000, 0.0000, 0.0000
+4, 0.0000, 0.0014, 0.0285
+5, 0.0000, 0.0000, 0.0001
+6, 0.0000, 0.0000, 0.0000
+10, 2.5016, 2.5982, 2.7952
+11, 0.2299, 0.5288, 1.2570
+12, 0.2845, 0.5943, 0.8713
+13, 0.3455, 0.3955, 0.5355
+14, 0.2955, 0.3814, 0.4431
+15, 0.2304, 0.5469, 0.9735
+16, 0.2761, 0.4723, 0.7460
+20, 2.5067, 2.5589, 2.6211
+21, 1.5797, 1.8261, 2.1204
+22, 1.8572, 1.9808, 2.1686
+23, 0.4198, 0.4877, 0.5202
+24, 0.4220, 0.4873, 0.5729
+25, 2.0504, 2.2492, 2.4038
+26, 2.1307, 2.3070, 2.4938
+30, 2.5003, 2.5565, 2.6215
+31, 0.5118, 0.6478, 0.8476
+32, 0.5157, 0.6690, 0.8628
+33, 0.1525, 0.2119, 0.2614
+34, 0.1407, 0.2039, 0.2715
+35, 0.5535, 0.6758, 0.8734
+36, 0.5397, 0.6877, 0.8979
+40, 2.4872, 2.5404, 2.5948
+41, 0.1454, 0.2044, 0.3307
+42, 0.1604, 0.2122, 0.2885
+43, 0.0287, 0.0484, 0.0962
+44, 0.0286, 0.0480, 0.1025
+45, 0.1542, 0.2173, 0.3335
+46, 0.1521, 0.2266, 0.3015
diff --git a/cocoalumberjack/Benchmarking/Results/Benchmark iPhone 3GS.csv b/cocoalumberjack/Benchmarking/Results/Benchmark iPhone 3GS.csv
new file mode 100644
index 0000000..8364ae0
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/Results/Benchmark iPhone 3GS.csv
@@ -0,0 +1,35 @@
+0, 4.2598, 4.3660, 4.6588
+1, 0.0000, 0.0000, 0.0000
+2, 0.0000, 0.0000, 0.0000
+3, 0.0000, 0.0000, 0.0000
+4, 0.0000, 0.0000, 0.0000
+5, 0.0000, 0.0000, 0.0000
+6, 0.0000, 0.0000, 0.0000
+10, 4.1986, 4.3513, 4.5088
+11, 0.5951, 1.5768, 2.3934
+12, 1.0329, 1.5809, 2.1354
+13, 0.2904, 0.3407, 0.4357
+14, 0.2413, 0.3309, 0.3610
+15, 0.6748, 1.2716, 1.6643
+16, 0.7571, 1.3473, 2.3276
+20, 4.2224, 4.3516, 4.5251
+21, 3.1635, 3.2748, 3.5510
+22, 3.2119, 3.2817, 3.4837
+23, 0.4739, 0.4894, 0.6311
+24, 0.4719, 0.4800, 0.5312
+25, 3.4027, 3.5172, 3.7449
+26, 3.3843, 3.4736, 3.7347
+30, 4.1721, 4.3516, 4.5596
+31, 0.9029, 1.2367, 1.5529
+32, 0.9005, 1.2779, 1.5703
+33, 0.1541, 0.1928, 0.2365
+34, 0.1533, 0.1986, 0.2383
+35, 0.8691, 1.1347, 1.5500
+36, 0.8477, 1.1970, 1.6069
+40, 4.1914, 4.3369, 4.6224
+41, 0.3008, 0.3281, 0.4669
+42, 0.2909, 0.3252, 0.4046
+43, 0.0470, 0.0478, 0.0488
+44, 0.0471, 0.0481, 0.0527
+45, 0.3151, 0.3484, 0.4229
+46, 0.3111, 0.3480, 0.4136
diff --git a/cocoalumberjack/Benchmarking/Results/Benchmark iPodTouch3 GCD.csv b/cocoalumberjack/Benchmarking/Results/Benchmark iPodTouch3 GCD.csv
new file mode 100644
index 0000000..caaa839
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/Results/Benchmark iPodTouch3 GCD.csv
@@ -0,0 +1,35 @@
+0, 4.7533, 4.8965, 5.1468
+1, 0.0000, 0.0000, 0.0000
+2, 0.0000, 0.0000, 0.0000
+3, 0.0000, 0.0000, 0.0000
+4, 0.0000, 0.0000, 0.0000
+5, 0.0000, 0.0000, 0.0000
+6, 0.0000, 0.0000, 0.0000
+10, 4.7739, 4.8658, 5.0876
+11, 0.1744, 0.7295, 1.3352
+12, 0.2169, 0.7354, 1.4760
+13, 0.1846, 0.2992, 0.4073
+14, 0.1816, 0.2980, 0.3568
+15, 0.1840, 0.8697, 1.5107
+16, 0.0834, 0.7477, 1.1599
+20, 4.7807, 4.9025, 5.1423
+21, 3.1918, 3.3175, 3.5093
+22, 3.1522, 3.2813, 3.5148
+23, 0.3052, 0.3182, 0.3852
+24, 0.3061, 0.3171, 0.3410
+25, 3.5837, 3.7104, 3.9758
+26, 3.5487, 3.6969, 3.9776
+30, 4.7613, 4.8927, 5.1026
+31, 0.8053, 1.0718, 1.3799
+32, 0.8069, 1.0888, 1.3447
+33, 0.1194, 0.1577, 0.1813
+34, 0.1249, 0.1523, 0.1777
+35, 0.9295, 1.2310, 1.4301
+36, 0.9240, 1.2278, 1.5636
+40, 4.8084, 4.8773, 5.0500
+41, 0.2822, 0.3202, 0.3642
+42, 0.3091, 0.3291, 0.3686
+43, 0.0302, 0.0313, 0.0341
+44, 0.0295, 0.0308, 0.0328
+45, 0.3281, 0.3669, 0.4102
+46, 0.3359, 0.3710, 0.3960
diff --git "a/cocoalumberjack/Benchmarking/Results/Lumberjack Benchmark \050PowerMac\051.ograph" "b/cocoalumberjack/Benchmarking/Results/Lumberjack Benchmark \050PowerMac\051.ograph"
new file mode 100644
index 0000000..8d5b3c7
--- /dev/null
+++ "b/cocoalumberjack/Benchmarking/Results/Lumberjack Benchmark \050PowerMac\051.ograph"
Binary files differ
diff --git "a/cocoalumberjack/Benchmarking/Results/Lumberjack Benchmark \050iMac\051.ograph" "b/cocoalumberjack/Benchmarking/Results/Lumberjack Benchmark \050iMac\051.ograph"
new file mode 100644
index 0000000..062c3c9
--- /dev/null
+++ "b/cocoalumberjack/Benchmarking/Results/Lumberjack Benchmark \050iMac\051.ograph"
Binary files differ
diff --git "a/cocoalumberjack/Benchmarking/Results/Lumberjack Benchmark \050iPad\051.ograph" "b/cocoalumberjack/Benchmarking/Results/Lumberjack Benchmark \050iPad\051.ograph"
new file mode 100644
index 0000000..267f5d0
--- /dev/null
+++ "b/cocoalumberjack/Benchmarking/Results/Lumberjack Benchmark \050iPad\051.ograph"
Binary files differ
diff --git "a/cocoalumberjack/Benchmarking/Results/Lumberjack Benchmark \050iPhone 3GS\051.ograph" "b/cocoalumberjack/Benchmarking/Results/Lumberjack Benchmark \050iPhone 3GS\051.ograph"
new file mode 100644
index 0000000..c797bfd
--- /dev/null
+++ "b/cocoalumberjack/Benchmarking/Results/Lumberjack Benchmark \050iPhone 3GS\051.ograph"
Binary files differ
diff --git a/cocoalumberjack/Benchmarking/StaticLogging.h b/cocoalumberjack/Benchmarking/StaticLogging.h
new file mode 100644
index 0000000..2a8c481
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/StaticLogging.h
@@ -0,0 +1,12 @@
+#import <Foundation/Foundation.h>
+
+
+@interface StaticLogging : NSObject
+
++ (void)speedTest0;
++ (void)speedTest1;
++ (void)speedTest2;
++ (void)speedTest3;
++ (void)speedTest4;
+
+@end
diff --git a/cocoalumberjack/Benchmarking/StaticLogging.m b/cocoalumberjack/Benchmarking/StaticLogging.m
new file mode 100644
index 0000000..2d0e3ca
--- /dev/null
+++ b/cocoalumberjack/Benchmarking/StaticLogging.m
@@ -0,0 +1,91 @@
+#import "StaticLogging.h"
+#import "PerformanceTesting.h"
+#import "DDLogMacros.h"
+
+#define FILENAME @"StaticLogging " // Trailing space to match exactly the others in length
+
+// Debug levels: off, error, warn, info, verbose
+static const DDLogLevel ddLogLevel = DDLogLevelWarning; // CONST
+
+
+@implementation StaticLogging
+
++ (void)speedTest0
+{
+ // Log statements that will not be executed due to log level
+
+ for (NSUInteger i = 0; i < SPEED_TEST_0_COUNT; i++)
+ {
+ DDLogVerbose(@"%@: SpeedTest0 - %lu", FILENAME, (unsigned long)i);
+ }
+}
+
++ (void)speedTest1
+{
+ // Log statements that will be executed asynchronously
+
+ for (NSUInteger i = 0; i < SPEED_TEST_1_COUNT; i++)
+ {
+ DDLogWarn(@"%@: SpeedTest1 - %lu", FILENAME, (unsigned long)i);
+ }
+}
+
++ (void)speedTest2
+{
+ // Log statements that will be executed synchronously
+
+ for (NSUInteger i = 0; i < SPEED_TEST_2_COUNT; i++)
+ {
+ DDLogError(@"%@: SpeedTest2 - %lu", FILENAME, (unsigned long)i);
+ }
+}
+
++ (void)speedTest3
+{
+ // Even Spread:
+ //
+ // 25% - Not executed due to log level
+ // 50% - Executed asynchronously
+ // 25% - Executed synchronously
+
+ for (NSUInteger i = 0; i < SPEED_TEST_3_COUNT; i++)
+ {
+ DDLogError(@"%@: SpeedTest3A - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_3_COUNT; i++)
+ {
+ DDLogWarn(@"%@: SpeedTest3B - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_3_COUNT; i++)
+ {
+ DDLogInfo(@"%@: SpeedTest3C - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_3_COUNT; i++)
+ {
+ DDLogVerbose(@"%@: SpeedTest3D - %lu", FILENAME, (unsigned long)i);
+ }
+}
+
++ (void)speedTest4
+{
+ // Custom Spread
+
+ for (NSUInteger i = 0; i < SPEED_TEST_4_ERROR_COUNT; i++)
+ {
+ DDLogError(@"%@: SpeedTest4A - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_4_WARN_COUNT; i++)
+ {
+ DDLogWarn(@"%@: SpeedTest4B - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_4_INFO_COUNT; i++)
+ {
+ DDLogInfo(@"%@: SpeedTest4C - %lu", FILENAME, (unsigned long)i);
+ }
+ for (NSUInteger i = 0; i < SPEED_TEST_4_VERBOSE_COUNT; i++)
+ {
+ DDLogVerbose(@"%@: SpeedTest4D - %lu", FILENAME, (unsigned long)i);
+ }
+}
+
+@end
diff --git a/cocoalumberjack/CHANGELOG.md b/cocoalumberjack/CHANGELOG.md
new file mode 100644
index 0000000..c3833dd
--- /dev/null
+++ b/cocoalumberjack/CHANGELOG.md
@@ -0,0 +1,274 @@
+## [2.2.0 - TVOS, Xcode7.1 on Oct 28th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.2.0)
+- added `tvOS` support (thanks [@sinoru](https://github.com/sinoru)) - [#634](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/634) [#640](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/640) [#630](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/630) [#628](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/628) [#618](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/618) [#611](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/611)
+- Remove `(escaping)` from the Swift `@autoclosure` parameters - [#642](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/642)
+
+## [2.1.0 - Swift 2.0, WatchOS, Xcode7 on Oct 23rd, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.1.0)
+- Fixed the version for the Carthage builds - see [#633](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/633)
+- Improved documentation
+
+## [2.1.0 RC - Swift 2.0, WatchOS, Xcode7 on Oct 22nd, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.1.0-rc)
+- Refactored the `NSDateFormatter` related code to fix a bunch of issues: [#621](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/621)
+- Fix Issue [#488](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/488): Support `DDLog` without `AppKit Dependency` (`#define DD_CLI`): [#627](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/627)
+- Re-add `NS_DESIGNATED_INITIALIZER` [#619](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/619)
+
+## [2.1.0 Beta - Swift 2.0, WatchOS, Xcode7 on Oct 12th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.1.0-beta)
+- Updated the library to use Swift 2.0 and Xcode 7 [#617](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/617) [#545](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/545) [#534](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/534)
+- WatchOS support (2.0) [#583](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/583) [#581](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/581) [#579](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/579)
+
+## [2.0.3 Patch for 2.0.0 on Oct 13th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.3)
+
+- Compatibility with Xcode 6 that was broken by the 2.0.2 patch - [f042fd3](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/f042fd3)
+
+## [2.0.2 Patch for 2.0.0 on Oct 12th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.2)
+
+- Swift 1.2 fixes [#546](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/546) [#578](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/578) plus and update to Swift 2.0 [5627dff](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/5627dff) imported from our swift_2.0 branch
+- Make build work on `tvOS` [#597](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/597)
+- Make `CocoaLumberjackSwift-iOS` target depends on `CocoaLumberjack-iOS` [#575](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/575)
+- `APPLICATION_EXTENSION_API_ONLY` to `YES` for Extensions [#576](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/576)
+- Remove unnecessary `NS_DESIGNATED_INITIALIZER`s [#593](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/593) fixes [#592](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/592)
+- Add ignore warning mark for `DDMakeColor` [#553](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/553)
+- Kill unused function warnings from `DDTTYLogger.h` [#613](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/613)
+- Flag unused parameters as being unused to silence strict warnings [#566](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/566)
+- Extend ignore unused warning pragma to cover all platforms [#559](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/559)
+- Removed images.xcassets from Mobile project [#580](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/580)
+- Silence the Xcode 7 upgrade check - [#595](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/595)
+- Fix import for when CL framework files are manually imported into project [#560](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/560)
+- Don't override defines in case they're already set at project level [#551](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/551)
+- log full filepath when failing to set attribute [#550](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/550)
+- Fix issue in standalone build with `DDLegacyMacros.h` [#552](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/552)
+- Update `CustomFormatters.md` with proper thread-safe blurb [#555](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/555)
+- typo in parameter's variable name fixed [#568](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/568)
+- Typo: minor fix [#571](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/571)
+- Surely we should be adding 1, not 0 for `OSAtomicAdd32` ? [#587](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/587)
+- `rollLogFileWithCompletionBlock` calls back on background queue instead of main queue [#589](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/589)
+- Removing extraneous `\` on line 55 [#600](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/600)
+- Updated `GettingStarted.md` to include `ddLogLevel` [#602](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/602)
+- Remove redundant check for `processorCount` availability [#604](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/604)
+
+## [2.0.1 Patch for 2.0.0 on Jun 25th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.1)
+
+- **Carthage support** [#521](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/521) [#526](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/526)
+- fixed crash on `DDASLLogCapture` when `TIME` or `TIME_NSEC` is `NULL` [#484](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/484)
+- **Swift** fixes and improvements: [#483](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/483) [#509](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/509) [#518](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/518) [#522](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/522) [5eafceb](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/5eafceb)
+- Unit tests: [#500](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/500) [#498](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/498) [#499](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/499)
+- Fix [#478](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/478) by reverting [#473](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/473)
+- Add `armv7s` to static library [#538](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/538)
+- Fix `NSLog` `threadid` mismatch with iOS 8+/OSX 10.10+ [#514](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/514)
+- Fixed the `LogV` macros so that avalist is no longer undefined [#511](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/511)
+- Using type safe `DDColor` alias instead of #define directive [#506](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/506)
+- Several fixes/tweaks to `DDASLLogCapture` [#512](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/512)
+- Prevent duplicate log entries when both `DDASLLogCapture` and `DDASLLogger` are used [#515](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/515)
+- Fix memory leaks in `DDTTYLogger`, add self annotations to blocks [#536](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/536)
+- Update older syntax to modern subscripting for array access [#482](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/482)
+- Remove execute permission on non-executable files [#517](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/517)
+- Change code samples to use `DDLogFlagWarning` [#520](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/520)
+- Fix seemingly obvious typo in the `toLogLevel` function [#508](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/508)
+
+## [CocoaLumberjack 2.0.0 on Mar 13th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0)
+
+The library was strongly refactored, with a few goals in mind:
+- Swift support - that we will release in a separate milestone, since CocoaPods 0.36.0 just got out
+- Unit tests support
+- reorganised things (on disk)
+- better coding style
+
+See [Migration from 1.x to 2.x](https://github.com/CocoaLumberjack/CocoaLumberjack#migrating-to-2x)
+
+## [2.0.0-rc2 on Feb 20th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-rc2)
+
+- Bucket of Swift improvements - [#434](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/434) [#437](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/437) [#449](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/449) [#440](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/440)
+- Fixed [#433](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/433) (build issue due to dispatch_queue properties) - [#455](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/455)
+- Enable codesign for iOS device framework builds - [#444](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/444)
+- Declare `automaticallyAppendNewlineForCustomFormatters` properties as `nonatomic` - [#443](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/443)
+- Warning fixes & type standardization - [#419](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/419)
+- Legacy checks updated - [#424](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/424)
+- Documentation updates
+
+## [2.0.0-rc on Dec 11th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-rc)
+
+- Fix `dispatch_queue_t` properties.
+- Fix `registeredClasses` crashes at launch.
+
+## [2.0.0-beta4 on Nov 7th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-beta4)
+
+- Major refactoring and clean up.
+- Remove superfluous `log` from property names and use underscore for direct variable access.
+- Preliminar Swift support through `CocoaLumberjack.swift`.
+- Automatic 1.9.x legacy support when `DDLog.h` is imported instead of the new `CocoaLumberjack.h`.
+
+## [2.0.0-beta3 on Oct 21st, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-beta3)
+
+- Modernize flag variables to be `NS_OPTIONS`/`NS_ENUM`.
+- Change the log flags and levels to `NSUInteger`.
+- Fix warning when compiled with assertions blocked.
+- Crash fixes.
+
+## [2.0.0-beta2 on Sep 30th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-beta2)
+
+- Cleanup code.
+- Match `NSLog` read UID functionality in `DDASLLogger`.
+- Update framework and static libraries.
+
+## [2.0.0 Beta on Aug 12th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-beta)
+
+See [Migrate from 1.x to 2.x](https://github.com/CocoaLumberjack/CocoaLumberjack#migrating-to-2x)
+
+## [1.9.2 Updated patch release for 1.9.0 on Aug 11th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.9.2)
+
+- Fixed `NSCalendar components:fromDate:` crash - [#140](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/140) [#307](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/307) [#216](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/216)
+- New `DDAssert` macros - [#306](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/306)
+- Limit log growth by disk space only, not the number of files - [#195](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/195) [#303](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/303)
+- Change the mechanism for adding new line character (i.e. '\n\) to log messages in some logger - [#308](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/308) [#310](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/310)
+- Fixed deprecations - [#320](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/320) [#312](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/312) [#317](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/317)
+- `aslmsg` not freed and causing memory leak - [#314](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/314)
+- Fixed `CompresingLogFileManager` compression bug - [#315](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/315)
+- Remove unnecessary `NULL` check before `free()` - [#316](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/316)
+
+## [1.9.1 Patch release for 1.9.0 on Jun 30th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.9.1)
+
+- Fixed issues in rolling frequency - [#243](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/243) [#295](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/295) [@georgekola](https://github.com/georgekola)
+- Fixed critical issue, `addLogger` method should use a full bit mask instead of `LOG_LEVEL_VERBOSE`, otherwise extended logs or extra flags are ignored [fe6824c](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/fe6824c) [@robbiehanson](https://github.com/robbiehanson)
+- Performance optimisation: use compiler macros to skip iOS version checks - [4656d3b](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/4656d3b) [#298](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/298) [#291](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/291) [@robbiehanson](https://github.com/robbiehanson) [@liviur](https://github.com/liviur)
+- Changed the `Build Active Architecture Only` to `NO` [#294](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/294) [#293](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/293)
+- Optimisation by reusing `NSDateFormatter` instances [#296](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/296) [#301](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/301)
+
+## [1.9.0 New ASL capture module, several File logger fixes on May 23rd, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.9.0)
+
+- New ASL capture module [#242](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/242) [#263](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/263)
+- Override default `NSFileProtection` handling [#285](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/285)
+- Replaced warnings when ARC was not enabled with errors [#284](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/284)
+- Fix for issue [#278](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/278) where really large log files can keep growing [#280](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/280)
+- Fixed Xcode warnings [#279](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/279)
+- Update `calendarUnitFlags` with new iOS SDK values [#277](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/277)
+- Fix possible crash in `[NSCalendar components:fromDate:]` [#277](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/277)
+- Fix [#262](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/262) inverted ifs when renaming log [#264](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/264)
+- Proper way of doing singletons (via `dispatch_once`) [#259](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/259)
+- Explicitly declare `DDFileLogger` and `DDDispatchQueueLogFormatter ` properties as atomic to avoid Xcode warnings [#258](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/258)
+- Set `NSFileProtectionKey` on the temporary file created during compression [#256](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/256)
+- Fix a rare crash in `CompressingLogFileManager` caused by an unchecked result from read [#255](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/255)
+- Add explicit casts for integer conversion [#253](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/253)
+- Replace use of `NSThread.detachNewThreadSelector` [#251](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/251)
+- Add a constructor override for `initWithLogsDirectory:` [#252](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/252)
+- Check and log the streamError whenever we fail to write during compression and log any failures when removing the original file or cleaning up the temporary file after compression failed [#250](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/250)
+- Following Apple's guidelines for iOS Static Libraries [#249](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/249)
+- Some extra warnings for the mobile framework xcode project [a2e5666](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/a2e5666)
+- Update `FineGrainedLoggingAppDelegate.m` [#244](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/244)
+- New `[DDLog log:message:]` primitive [7f8af2e](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/7f8af2e)
+- Fixed issue [#181](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/181) when logging messages in iOS7 devices aren't properly retrieved by `asl_search` [#240](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/240)
+- Allow prevention of log file reuse [#238](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/238)
+- `DDTTYLogger`: Favour XcodeColors environment variable [#237](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/237)
+- `DDLog`: calling `atexit_b` in CLI applications, that use Foundation framework [#234](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/234)
+
+## [1.8.1 AllLoggers and bugfixes on Feb 14th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.8.1)
+
+- read access to all loggers - [#217](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/217) [#219](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/219)
+- fixed bug with archived logs not being handled correctly on iOS simulator - [#218](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/218)
+- log the `strerror(errno)` value when `setxattr()` fails - [#211](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/211)
+- Add a check for an archived log before overwriting - [#214](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/214)
+- improved safety by using assertions instead of comments (`DDLog` in the core) - [#221](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/221)
+- added Lumberjack logo :)
+
+## [1.8.0 Better CL support, custom logfile name format, bugfixes on Jan 21st, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.8.0)
+
+- `DDFileLogger` custom logfile (name) format - [#208](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/208)
+- Security static analysis fix - [#202](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/202)
+- `DDFileLogger`: using `CFBundleIdentifier` as a log filename prefix on OSX and iOS - [#206](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/206)
+- Allow disabling of specific levels per-logger - [#204](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/204)
+- Improve support for OS X command line tools - [#194](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/194)
+- `DDFileLogger`: fixed crash that occurred in case if application name == nil - [#198](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/198)
+- `DDFileLogger`: fixed comment - [#199](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/199)
+- Fix Travis - [#205](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/205)
+
+## [1.7.0 New log file naming convention and CocoaLumberjack organisation on Dec 10th, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.7.0)
+
+- new log file naming convention - [#191](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/191)
+- completed transition to **CocoaLumberjack** organisation - [#188](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/188)
+
+## [1.6.5.1 Patch release for Xcode 4.4+ compatibility on Dec 4th, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.5.1)
+
+- fixed compatibility with Xcode 4.4+ [#187](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/187)
+
+## [1.6.5 File Logger refactoring, Multi Formatter, preffixed extension classes on Dec 3rd, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.5)
+
+`DDFileLogger` refactoring and fixes (thanks [@dvor](https://github.com/dvor) and [@an0](https://github.com/an0)):
+- Fixed [#63](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/63) Loggers don't flush in Command Line Tool [#184](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/184)
+- Fixed [#52](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/52) Force log rotation [#183](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/183)
+- Fixed [#55](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/55) After deleting log file or log dir they aren't created again without relaunching the app [#183](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/183)
+- Fixed [#129](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/129) [iOS] `DDFileLogger` causes crash when logging from background app [#183](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/183)
+- Fixed [#153](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/153) Log file on iPhone only contains a single line [#177](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/177)
+- Fixed [#155](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/155) How do I combine all my log levels into one file? [#177](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/177)
+- Fixed [#175](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/175) `DFileLogger` `creationDate` bug on 64-bit iOS system [#177](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/177)
+- Allow customizing the naming convention for log files to use timestamps [#174](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/174)
+
+Other:
+- Implemented multiple formatter (`DDMultiFormatter` - alows chaining of formatters) [#178](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/178)
+- Added DD preffix to extension classes (`ContextFilterLogFormatter` and `DispatchQueueLogFormatter`) [#178](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/178)
+- Updated code indentation: Tabs changed to spaces [#180](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/180)
+- Included `DDLog+LOGV.h` in Cocoapods sources [d253bd7](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/d253bd7)
+- other fixes/improvements
+
+## [1.6.4 Fix compatibility with 3rd party frameworks on Nov 21st, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.4)
+
+* "Fix" conflicts with 3rd party libraries using `CocoaLumberjack` [#172](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/172)
+* Ignore deprecated warning for `dispatch_get_current_queue` [#167](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/167)
+* Add new `DEBUG` log level support to included loggers [#166](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/166)
+* Method declarations that make it easier to extend/modify `DispatchQueueLogFormatter` [#164](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/164)
+
+## [1.6.3 New macros, updated podspec and bug fixes on Apr 2nd, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.3)
+
+* Add `LOGV`-style macros [#161](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/161)
+* Fix getting queue's label [#159](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/159)
+* New log level `DEBUG` [#145](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/145)
+* Use `DISPATCH_CURRENT_QUEUE_LABEL` if available [#159](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/159)
+* Different `logLevel` per each logger [#151](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/151)
+* Created 2 subspecs, `Core` and `Extensions` [#152](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/152)
+* Updated observer for keypath using `NSStringFromSelector` + `@selector` [38e5da3](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/38e5da3)
+* Replaced `id` return type with `instancetype` [ebee454](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/ebee454)
+* Remove implicit conversion warnings [#149](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/149)
+* `DDTTYLogger`: Allow to set default color profiles for all contexts at once [#146](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/146) [#158](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/158)
+* `DDTTYLogger`: By default apply `setForegroundColor:backgroundColor:forFlag:` to `LOG_CONTEXT_ALL` [#154](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/154)
+* `DispatchQueueLogFormatter`: Use modern Objective-C [#142](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/142)
+* `DispatchQueueLogFormatter`: Make sure to always use a `NSGregorianCalendar` for date formatter [#142](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/142)
+* Replaced explicit reference to class name in `logFileWithPath` factory method [#131](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/131)
+* Catch exceptions in `logMessage:` [#130](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/130)
+* Fix enum type conversion warnings [#124](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/124)
+* Add deployment target condition for workaround [#121](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/121)
+* Fix static analyzer warnings about `nil` values in dictionary [#122](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/122)
+* Fix `dispatch_get_current_queue` crash [#121](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/121)
+* Fixing colors in greyscale color-space not working [d019cfd](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/d019cfd)
+* Guard around `dispatch_resume()` being called with null pointer [#107](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/107)
+* `NULL` safety checks [#107](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/107)
+
+## [1.6.2 on Apr 2nd, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.2)
+
+## [1.6.1 on Apr 2nd, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.1)
+
+## [1.6 on Jul 3rd, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6)
+
+## [1.5.1 on Jul 3rd, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.5.1)
+
+## [1.5 on Jul 3rd, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.5)
+
+## [1.4.1 on Jul 3rd, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.4.1)
+
+## [1.4 on Jul 3rd, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.4)
+
+## [1.3.3 on Mar 30th, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.3.3)
+
+## [1.3.2 on Dec 23rd, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.3.2)
+
+## [1.3.1 on Dec 9th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.3.1)
+
+## [1.3 on Dec 9th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.3)
+
+## [1.2.3 on Dec 9th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.2.3)
+
+## [1.2.2 on Dec 9th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.2.2)
+
+## [1.2.1 on Oct 13th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.2.1)
+
+## [1.2 on Oct 13th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.2)
+
+## [1.1 on Oct 13th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.1)
+
+## [1.0 on Oct 13th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.0)
diff --git a/cocoalumberjack/Classes/._CLI b/cocoalumberjack/Classes/._CLI
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._CLI
Binary files differ
diff --git a/cocoalumberjack/Classes/._CocoaLumberjack.h b/cocoalumberjack/Classes/._CocoaLumberjack.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._CocoaLumberjack.h
Binary files differ
diff --git a/cocoalumberjack/Classes/._CocoaLumberjack.swift b/cocoalumberjack/Classes/._CocoaLumberjack.swift
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._CocoaLumberjack.swift
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDASLLogCapture.h b/cocoalumberjack/Classes/._DDASLLogCapture.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDASLLogCapture.h
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDASLLogCapture.m b/cocoalumberjack/Classes/._DDASLLogCapture.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDASLLogCapture.m
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDASLLogger.h b/cocoalumberjack/Classes/._DDASLLogger.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDASLLogger.h
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDASLLogger.m b/cocoalumberjack/Classes/._DDASLLogger.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDASLLogger.m
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDAbstractDatabaseLogger.h b/cocoalumberjack/Classes/._DDAbstractDatabaseLogger.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDAbstractDatabaseLogger.h
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDAbstractDatabaseLogger.m b/cocoalumberjack/Classes/._DDAbstractDatabaseLogger.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDAbstractDatabaseLogger.m
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDAssertMacros.h b/cocoalumberjack/Classes/._DDAssertMacros.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDAssertMacros.h
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDFileLogger.h b/cocoalumberjack/Classes/._DDFileLogger.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDFileLogger.h
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDFileLogger.m b/cocoalumberjack/Classes/._DDFileLogger.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDFileLogger.m
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDLegacyMacros.h b/cocoalumberjack/Classes/._DDLegacyMacros.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDLegacyMacros.h
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDLog+LOGV.h b/cocoalumberjack/Classes/._DDLog+LOGV.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDLog+LOGV.h
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDLog.h b/cocoalumberjack/Classes/._DDLog.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDLog.h
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDLog.m b/cocoalumberjack/Classes/._DDLog.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDLog.m
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDLogMacros.h b/cocoalumberjack/Classes/._DDLogMacros.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDLogMacros.h
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDTTYLogger.h b/cocoalumberjack/Classes/._DDTTYLogger.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDTTYLogger.h
Binary files differ
diff --git a/cocoalumberjack/Classes/._DDTTYLogger.m b/cocoalumberjack/Classes/._DDTTYLogger.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._DDTTYLogger.m
Binary files differ
diff --git a/cocoalumberjack/Classes/._Extensions b/cocoalumberjack/Classes/._Extensions
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/._Extensions
Binary files differ
diff --git a/cocoalumberjack/Classes/CLI/._CLIColor.h b/cocoalumberjack/Classes/CLI/._CLIColor.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/CLI/._CLIColor.h
Binary files differ
diff --git a/cocoalumberjack/Classes/CLI/._CLIColor.m b/cocoalumberjack/Classes/CLI/._CLIColor.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/CLI/._CLIColor.m
Binary files differ
diff --git a/cocoalumberjack/Classes/CLI/CLIColor.h b/cocoalumberjack/Classes/CLI/CLIColor.h
new file mode 100644
index 0000000..cfa0909
--- /dev/null
+++ b/cocoalumberjack/Classes/CLI/CLIColor.h
@@ -0,0 +1,44 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import <Foundation/Foundation.h>
+#import <QuartzCore/QuartzCore.h>
+
+/**
+ * This class represents an NSColor replacement for CLI projects that don't link with AppKit
+ **/
+@interface CLIColor : NSObject
+
+/**
+ * Convenience method for creating a `CLIColor` instance from RGBA params
+ *
+ * @param red red channel, between 0 and 1
+ * @param green green channel, between 0 and 1
+ * @param blue blue channel, between 0 and 1
+ * @param alpha alpha channel, between 0 and 1
+ */
++ (CLIColor *)colorWithCalibratedRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
+
+/**
+ * Get the RGBA components from a `CLIColor`
+ *
+ * @param red red channel, between 0 and 1
+ * @param green green channel, between 0 and 1
+ * @param blue blue channel, between 0 and 1
+ * @param alpha alpha channel, between 0 and 1
+ */
+- (void)getRed:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha;
+
+@end
diff --git a/cocoalumberjack/Classes/CLI/CLIColor.m b/cocoalumberjack/Classes/CLI/CLIColor.m
new file mode 100644
index 0000000..c952c18
--- /dev/null
+++ b/cocoalumberjack/Classes/CLI/CLIColor.m
@@ -0,0 +1,55 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import "CLIColor.h"
+
+@interface CLIColor () {
+ CGFloat _red, _green, _blue, _alpha;
+}
+
+@end
+
+
+@implementation CLIColor
+
++ (CLIColor *)colorWithCalibratedRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha {
+ CLIColor *color = [CLIColor new];
+
+ color->_red = red;
+ color->_green = green;
+ color->_blue = blue;
+ color->_alpha = alpha;
+ return color;
+}
+
+- (void)getRed:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha {
+ if (red) {
+ *red = _red;
+ }
+
+ if (green) {
+ *green = _green;
+ }
+
+ if (blue) {
+ *blue = _blue;
+ }
+
+ if (alpha) {
+ *alpha = _alpha;
+ }
+}
+
+@end
diff --git a/cocoalumberjack/Classes/CocoaLumberjack.h b/cocoalumberjack/Classes/CocoaLumberjack.h
new file mode 100644
index 0000000..0b568fb
--- /dev/null
+++ b/cocoalumberjack/Classes/CocoaLumberjack.h
@@ -0,0 +1,81 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+/**
+ * Welcome to CocoaLumberjack!
+ *
+ * The project page has a wealth of documentation if you have any questions.
+ * https://github.com/CocoaLumberjack/CocoaLumberjack
+ *
+ * If you're new to the project you may wish to read "Getting Started" at:
+ * Documentation/GettingStarted.md
+ *
+ * Otherwise, here is a quick refresher.
+ * There are three steps to using the macros:
+ *
+ * Step 1:
+ * Import the header in your implementation or prefix file:
+ *
+ * #import <CocoaLumberjack/CocoaLumberjack.h>
+ *
+ * Step 2:
+ * Define your logging level in your implementation file:
+ *
+ * // Log levels: off, error, warn, info, verbose
+ * static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
+ *
+ * Step 2 [3rd party frameworks]:
+ *
+ * Define your LOG_LEVEL_DEF to a different variable/function than ddLogLevel:
+ *
+ * // #undef LOG_LEVEL_DEF // Undefine first only if needed
+ * #define LOG_LEVEL_DEF myLibLogLevel
+ *
+ * Define your logging level in your implementation file:
+ *
+ * // Log levels: off, error, warn, info, verbose
+ * static const DDLogLevel myLibLogLevel = DDLogLevelVerbose;
+ *
+ * Step 3:
+ * Replace your NSLog statements with DDLog statements according to the severity of the message.
+ *
+ * NSLog(@"Fatal error, no dohickey found!"); -> DDLogError(@"Fatal error, no dohickey found!");
+ *
+ * DDLog works exactly the same as NSLog.
+ * This means you can pass it multiple variables just like NSLog.
+ **/
+
+#import <Foundation/Foundation.h>
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+ #define DD_LEGACY_MACROS 0
+#endif
+
+// Core
+#import "DDLog.h"
+
+// Main macros
+#import "DDLogMacros.h"
+#import "DDAssertMacros.h"
+
+// Capture ASL
+#import "DDASLLogCapture.h"
+
+// Loggers
+#import "DDTTYLogger.h"
+#import "DDASLLogger.h"
+#import "DDFileLogger.h"
+
diff --git a/cocoalumberjack/Classes/CocoaLumberjack.swift b/cocoalumberjack/Classes/CocoaLumberjack.swift
new file mode 100644
index 0000000..5f022ce
--- /dev/null
+++ b/cocoalumberjack/Classes/CocoaLumberjack.swift
@@ -0,0 +1,91 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2014-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+import Foundation
+
+extension DDLogFlag {
+ public static func fromLogLevel(logLevel: DDLogLevel) -> DDLogFlag {
+ return DDLogFlag(rawValue: logLevel.rawValue)
+ }
+
+ public init(_ logLevel: DDLogLevel) {
+ self = DDLogFlag(rawValue: logLevel.rawValue)
+ }
+
+ ///returns the log level, or the lowest equivalant.
+ public func toLogLevel() -> DDLogLevel {
+ if let ourValid = DDLogLevel(rawValue: self.rawValue) {
+ return ourValid
+ } else {
+ let logFlag:DDLogFlag = self
+
+ if logFlag.contains(.Verbose) {
+ return .Verbose
+ } else if logFlag.contains(.Debug) {
+ return .Debug
+ } else if logFlag.contains(.Info) {
+ return .Info
+ } else if logFlag.contains(.Warning) {
+ return .Warning
+ } else if logFlag.contains(.Error) {
+ return .Error
+ } else {
+ return .Off
+ }
+ }
+ }
+}
+
+public var defaultDebugLevel = DDLogLevel.Verbose
+
+public func resetDefaultDebugLevel() {
+ defaultDebugLevel = DDLogLevel.Verbose
+}
+
+public func SwiftLogMacro(isAsynchronous: Bool, level: DDLogLevel, flag flg: DDLogFlag, context: Int = 0, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, tag: AnyObject? = nil, @autoclosure string: () -> String) {
+ if level.rawValue & flg.rawValue != 0 {
+ // Tell the DDLogMessage constructor to copy the C strings that get passed to it.
+ // Using string interpolation to prevent integer overflow warning when using StaticString.stringValue
+ let logMessage = DDLogMessage(message: string(), level: level, flag: flg, context: context, file: "\(file)", function: "\(function)", line: line, tag: tag, options: [.CopyFile, .CopyFunction], timestamp: nil)
+ DDLog.log(isAsynchronous, message: logMessage)
+ }
+}
+
+public func DDLogDebug(@autoclosure logText: () -> String, level: DDLogLevel = defaultDebugLevel, context: Int = 0, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, tag: AnyObject? = nil, asynchronous async: Bool = true) {
+ SwiftLogMacro(async, level: level, flag: .Debug, context: context, file: file, function: function, line: line, tag: tag, string: logText)
+}
+
+public func DDLogInfo(@autoclosure logText: () -> String, level: DDLogLevel = defaultDebugLevel, context: Int = 0, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, tag: AnyObject? = nil, asynchronous async: Bool = true) {
+ SwiftLogMacro(async, level: level, flag: .Info, context: context, file: file, function: function, line: line, tag: tag, string: logText)
+}
+
+public func DDLogWarn(@autoclosure logText: () -> String, level: DDLogLevel = defaultDebugLevel, context: Int = 0, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, tag: AnyObject? = nil, asynchronous async: Bool = true) {
+ SwiftLogMacro(async, level: level, flag: .Warning, context: context, file: file, function: function, line: line, tag: tag, string: logText)
+}
+
+public func DDLogVerbose(@autoclosure logText: () -> String, level: DDLogLevel = defaultDebugLevel, context: Int = 0, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, tag: AnyObject? = nil, asynchronous async: Bool = true) {
+ SwiftLogMacro(async, level: level, flag: .Verbose, context: context, file: file, function: function, line: line, tag: tag, string: logText)
+}
+
+public func DDLogError(@autoclosure logText: () -> String, level: DDLogLevel = defaultDebugLevel, context: Int = 0, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, tag: AnyObject? = nil, asynchronous async: Bool = false) {
+ SwiftLogMacro(async, level: level, flag: .Error, context: context, file: file, function: function, line: line, tag: tag, string: logText)
+}
+
+/// Analogous to the C preprocessor macro `THIS_FILE`.
+public func CurrentFileName(fileName: StaticString = __FILE__) -> String {
+ // Using string interpolation to prevent integer overflow warning when using StaticString.stringValue
+ // This double-casting to NSString is necessary as changes to how Swift handles NSPathUtilities requres the string to be an NSString
+ return (("\(fileName)" as NSString).lastPathComponent as NSString).stringByDeletingPathExtension
+}
diff --git a/cocoalumberjack/Classes/DDASLLogCapture.h b/cocoalumberjack/Classes/DDASLLogCapture.h
new file mode 100644
index 0000000..f7fa79f
--- /dev/null
+++ b/cocoalumberjack/Classes/DDASLLogCapture.h
@@ -0,0 +1,48 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import "DDASLLogger.h"
+
+@protocol DDLogger;
+
+/**
+ * This class provides the ability to capture the ASL (Apple System Logs)
+ */
+@interface DDASLLogCapture : NSObject
+
+/**
+ * Start capturing logs
+ */
++ (void)start;
+
+/**
+ * Stop capturing logs
+ */
++ (void)stop;
+
+/**
+ * Returns the current capture level.
+ * @note Default log level: DDLogLevelVerbose (i.e. capture all ASL messages).
+ */
++ (DDLogLevel)captureLevel;
+
+/**
+ * Set the capture level
+ *
+ * @param level new level
+ */
++ (void)setCaptureLevel:(DDLogLevel)level;
+
+@end
diff --git a/cocoalumberjack/Classes/DDASLLogCapture.m b/cocoalumberjack/Classes/DDASLLogCapture.m
new file mode 100644
index 0000000..98d5342
--- /dev/null
+++ b/cocoalumberjack/Classes/DDASLLogCapture.m
@@ -0,0 +1,230 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import "DDASLLogCapture.h"
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+ #define DD_LEGACY_MACROS 0
+#endif
+
+#import "DDLog.h"
+
+#include <asl.h>
+#include <notify.h>
+#include <notify_keys.h>
+#include <sys/time.h>
+
+static BOOL _cancel = YES;
+static DDLogLevel _captureLevel = DDLogLevelVerbose;
+
+#ifdef __IPHONE_8_0
+ #define DDASL_IOS_PIVOT_VERSION __IPHONE_8_0
+#endif
+#ifdef __MAC_10_10
+ #define DDASL_OSX_PIVOT_VERSION __MAC_10_10
+#endif
+
+@implementation DDASLLogCapture
+
+static aslmsg (*dd_asl_next)(aslresponse obj);
+static void (*dd_asl_release)(aslresponse obj);
+
++ (void)initialize
+{
+ #if (defined(DDASL_IOS_PIVOT_VERSION) && __IPHONE_OS_VERSION_MAX_ALLOWED >= DDASL_IOS_PIVOT_VERSION) || (defined(DDASL_OSX_PIVOT_VERSION) && __MAC_OS_X_VERSION_MAX_ALLOWED >= DDASL_OSX_PIVOT_VERSION)
+ #if __IPHONE_OS_VERSION_MIN_REQUIRED < DDASL_IOS_PIVOT_VERSION || __MAC_OS_X_VERSION_MIN_REQUIRED < DDASL_OSX_PIVOT_VERSION
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ // Building on falsely advertised SDK, targeting deprecated API
+ dd_asl_next = &aslresponse_next;
+ dd_asl_release = &aslresponse_free;
+ #pragma GCC diagnostic pop
+ #else
+ // Building on lastest, correct SDK, targeting latest API
+ dd_asl_next = &asl_next;
+ dd_asl_release = &asl_release;
+ #endif
+ #else
+ // Building on old SDKs, targeting deprecated API
+ dd_asl_next = &aslresponse_next;
+ dd_asl_release = &aslresponse_free;
+ #endif
+}
+
++ (void)start {
+ // Ignore subsequent calls
+ if (!_cancel) {
+ return;
+ }
+
+ _cancel = NO;
+
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
+ [self captureAslLogs];
+ });
+}
+
++ (void)stop {
+ _cancel = YES;
+}
+
++ (DDLogLevel)captureLevel {
+ return _captureLevel;
+}
+
++ (void)setCaptureLevel:(DDLogLevel)level {
+ _captureLevel = level;
+}
+
+#pragma mark - Private methods
+
++ (void)configureAslQuery:(aslmsg)query {
+ const char param[] = "7"; // ASL_LEVEL_DEBUG, which is everything. We'll rely on regular DDlog log level to filter
+
+ asl_set_query(query, ASL_KEY_LEVEL, param, ASL_QUERY_OP_LESS_EQUAL | ASL_QUERY_OP_NUMERIC);
+
+ // Don't retrieve logs from our own DDASLLogger
+ asl_set_query(query, kDDASLKeyDDLog, kDDASLDDLogValue, ASL_QUERY_OP_NOT_EQUAL);
+
+#if !TARGET_OS_IPHONE || TARGET_SIMULATOR
+ int processId = [[NSProcessInfo processInfo] processIdentifier];
+ char pid[16];
+ sprintf(pid, "%d", processId);
+ asl_set_query(query, ASL_KEY_PID, pid, ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_NUMERIC);
+#endif
+}
+
++ (void)aslMessageReceived:(aslmsg)msg {
+ const char* messageCString = asl_get( msg, ASL_KEY_MSG );
+ if ( messageCString == NULL )
+ return;
+
+ int flag;
+ BOOL async;
+
+ const char* levelCString = asl_get(msg, ASL_KEY_LEVEL);
+ switch (levelCString? atoi(levelCString) : 0) {
+ // By default all NSLog's with a ASL_LEVEL_WARNING level
+ case ASL_LEVEL_EMERG :
+ case ASL_LEVEL_ALERT :
+ case ASL_LEVEL_CRIT : flag = DDLogFlagError; async = NO; break;
+ case ASL_LEVEL_ERR : flag = DDLogFlagWarning; async = YES; break;
+ case ASL_LEVEL_WARNING : flag = DDLogFlagInfo; async = YES; break;
+ case ASL_LEVEL_NOTICE : flag = DDLogFlagDebug; async = YES; break;
+ case ASL_LEVEL_INFO :
+ case ASL_LEVEL_DEBUG :
+ default : flag = DDLogFlagVerbose; async = YES; break;
+ }
+
+ if (!(_captureLevel & flag)) {
+ return;
+ }
+
+ // NSString * sender = [NSString stringWithCString:asl_get(msg, ASL_KEY_SENDER) encoding:NSUTF8StringEncoding];
+ NSString *message = @(messageCString);
+
+ const char* secondsCString = asl_get( msg, ASL_KEY_TIME );
+ const char* nanoCString = asl_get( msg, ASL_KEY_TIME_NSEC );
+ NSTimeInterval seconds = secondsCString ? strtod(secondsCString, NULL) : [NSDate timeIntervalSinceReferenceDate] - NSTimeIntervalSince1970;
+ double nanoSeconds = nanoCString? strtod(nanoCString, NULL) : 0;
+ NSTimeInterval totalSeconds = seconds + (nanoSeconds / 1e9);
+
+ NSDate *timeStamp = [NSDate dateWithTimeIntervalSince1970:totalSeconds];
+
+ DDLogMessage *logMessage = [[DDLogMessage alloc]initWithMessage:message
+ level:_captureLevel
+ flag:flag
+ context:0
+ file:@"DDASLLogCapture"
+ function:0
+ line:0
+ tag:nil
+ options:0
+ timestamp:timeStamp];
+
+ [DDLog log:async message:logMessage];
+}
+
++ (void)captureAslLogs {
+ @autoreleasepool
+ {
+ /*
+ We use ASL_KEY_MSG_ID to see each message once, but there's no
+ obvious way to get the "next" ID. To bootstrap the process, we'll
+ search by timestamp until we've seen a message.
+ */
+
+ struct timeval timeval = {
+ .tv_sec = 0
+ };
+ gettimeofday(&timeval, NULL);
+ unsigned long long startTime = timeval.tv_sec;
+ __block unsigned long long lastSeenID = 0;
+
+ /*
+ syslogd posts kNotifyASLDBUpdate (com.apple.system.logger.message)
+ through the notify API when it saves messages to the ASL database.
+ There is some coalescing - currently it is sent at most twice per
+ second - but there is no documented guarantee about this. In any
+ case, there may be multiple messages per notification.
+
+ Notify notifications don't carry any payload, so we need to search
+ for the messages.
+ */
+ int notifyToken = 0; // Can be used to unregister with notify_cancel().
+ notify_register_dispatch(kNotifyASLDBUpdate, ¬ifyToken, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(int token)
+ {
+ // At least one message has been posted; build a search query.
+ @autoreleasepool
+ {
+ aslmsg query = asl_new(ASL_TYPE_QUERY);
+ char stringValue[64];
+
+ if (lastSeenID > 0) {
+ snprintf(stringValue, sizeof stringValue, "%llu", lastSeenID);
+ asl_set_query(query, ASL_KEY_MSG_ID, stringValue, ASL_QUERY_OP_GREATER | ASL_QUERY_OP_NUMERIC);
+ } else {
+ snprintf(stringValue, sizeof stringValue, "%llu", startTime);
+ asl_set_query(query, ASL_KEY_TIME, stringValue, ASL_QUERY_OP_GREATER_EQUAL | ASL_QUERY_OP_NUMERIC);
+ }
+
+ [self configureAslQuery:query];
+
+ // Iterate over new messages.
+ aslmsg msg;
+ aslresponse response = asl_search(NULL, query);
+
+ while ((msg = dd_asl_next(response)))
+ {
+ [self aslMessageReceived:msg];
+
+ // Keep track of which messages we've seen.
+ lastSeenID = atoll(asl_get(msg, ASL_KEY_MSG_ID));
+ }
+ dd_asl_release(response);
+ asl_free(query);
+
+ if (_cancel) {
+ notify_cancel(token);
+ return;
+ }
+
+ }
+ });
+ }
+}
+
+@end
diff --git a/cocoalumberjack/Classes/DDASLLogger.h b/cocoalumberjack/Classes/DDASLLogger.h
new file mode 100644
index 0000000..24cc1c3
--- /dev/null
+++ b/cocoalumberjack/Classes/DDASLLogger.h
@@ -0,0 +1,58 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import <Foundation/Foundation.h>
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+ #define DD_LEGACY_MACROS 0
+#endif
+
+#import "DDLog.h"
+
+// Custom key set on messages sent to ASL
+extern const char* const kDDASLKeyDDLog;
+
+// Value set for kDDASLKeyDDLog
+extern const char* const kDDASLDDLogValue;
+
+/**
+ * This class provides a logger for the Apple System Log facility.
+ *
+ * As described in the "Getting Started" page,
+ * the traditional NSLog() function directs its output to two places:
+ *
+ * - Apple System Log
+ * - StdErr (if stderr is a TTY) so log statements show up in Xcode console
+ *
+ * To duplicate NSLog() functionality you can simply add this logger and a tty logger.
+ * However, if you instead choose to use file logging (for faster performance),
+ * you may choose to use a file logger and a tty logger.
+ **/
+@interface DDASLLogger : DDAbstractLogger <DDLogger>
+
+/**
+ * Singleton method
+ *
+ * @return the shared instance
+ */
++ (instancetype)sharedInstance;
+
+// Inherited from DDAbstractLogger
+
+// - (id <DDLogFormatter>)logFormatter;
+// - (void)setLogFormatter:(id <DDLogFormatter>)formatter;
+
+@end
diff --git a/cocoalumberjack/Classes/DDASLLogger.m b/cocoalumberjack/Classes/DDASLLogger.m
new file mode 100644
index 0000000..edce218
--- /dev/null
+++ b/cocoalumberjack/Classes/DDASLLogger.m
@@ -0,0 +1,121 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import "DDASLLogger.h"
+#import <asl.h>
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+const char* const kDDASLKeyDDLog = "DDLog";
+
+const char* const kDDASLDDLogValue = "1";
+
+static DDASLLogger *sharedInstance;
+
+@interface DDASLLogger () {
+ aslclient _client;
+}
+
+@end
+
+
+@implementation DDASLLogger
+
++ (instancetype)sharedInstance {
+ static dispatch_once_t DDASLLoggerOnceToken;
+
+ dispatch_once(&DDASLLoggerOnceToken, ^{
+ sharedInstance = [[[self class] alloc] init];
+ });
+
+ return sharedInstance;
+}
+
+- (instancetype)init {
+ if (sharedInstance != nil) {
+ return nil;
+ }
+
+ if ((self = [super init])) {
+ // A default asl client is provided for the main thread,
+ // but background threads need to create their own client.
+
+ _client = asl_open(NULL, "com.apple.console", 0);
+ }
+
+ return self;
+}
+
+- (void)logMessage:(DDLogMessage *)logMessage {
+ // Skip captured log messages
+ if ([logMessage->_fileName isEqualToString:@"DDASLLogCapture"]) {
+ return;
+ }
+
+ NSString * message = _logFormatter ? [_logFormatter formatLogMessage:logMessage] : logMessage->_message;
+
+ if (message) {
+ const char *msg = [message UTF8String];
+
+ size_t aslLogLevel;
+ switch (logMessage->_flag) {
+ // Note: By default ASL will filter anything above level 5 (Notice).
+ // So our mappings shouldn't go above that level.
+ case DDLogFlagError : aslLogLevel = ASL_LEVEL_CRIT; break;
+ case DDLogFlagWarning : aslLogLevel = ASL_LEVEL_ERR; break;
+ case DDLogFlagInfo : aslLogLevel = ASL_LEVEL_WARNING; break; // Regular NSLog's level
+ case DDLogFlagDebug :
+ case DDLogFlagVerbose :
+ default : aslLogLevel = ASL_LEVEL_NOTICE; break;
+ }
+
+ static char const *const level_strings[] = { "0", "1", "2", "3", "4", "5", "6", "7" };
+
+ // NSLog uses the current euid to set the ASL_KEY_READ_UID.
+ uid_t const readUID = geteuid();
+
+ char readUIDString[16];
+#ifndef NS_BLOCK_ASSERTIONS
+ int l = snprintf(readUIDString, sizeof(readUIDString), "%d", readUID);
+#else
+ snprintf(readUIDString, sizeof(readUIDString), "%d", readUID);
+#endif
+
+ NSAssert(l < sizeof(readUIDString),
+ @"Formatted euid is too long.");
+ NSAssert(aslLogLevel < (sizeof(level_strings) / sizeof(level_strings[0])),
+ @"Unhandled ASL log level.");
+
+ aslmsg m = asl_new(ASL_TYPE_MSG);
+ if (m != NULL) {
+ if (asl_set(m, ASL_KEY_LEVEL, level_strings[aslLogLevel]) == 0 &&
+ asl_set(m, ASL_KEY_MSG, msg) == 0 &&
+ asl_set(m, ASL_KEY_READ_UID, readUIDString) == 0 &&
+ asl_set(m, kDDASLKeyDDLog, kDDASLDDLogValue) == 0) {
+ asl_send(_client, m);
+ }
+ asl_free(m);
+ }
+ //TODO handle asl_* failures non-silently?
+ }
+}
+
+- (NSString *)loggerName {
+ return @"cocoa.lumberjack.aslLogger";
+}
+
+@end
diff --git a/cocoalumberjack/Classes/DDAbstractDatabaseLogger.h b/cocoalumberjack/Classes/DDAbstractDatabaseLogger.h
new file mode 100644
index 0000000..aad3666
--- /dev/null
+++ b/cocoalumberjack/Classes/DDAbstractDatabaseLogger.h
@@ -0,0 +1,123 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+ #define DD_LEGACY_MACROS 0
+#endif
+
+#import "DDLog.h"
+
+/**
+ * This class provides an abstract implementation of a database logger.
+ *
+ * That is, it provides the base implementation for a database logger to build atop of.
+ * All that is needed for a concrete database logger is to extend this class
+ * and override the methods in the implementation file that are prefixed with "db_".
+ **/
+@interface DDAbstractDatabaseLogger : DDAbstractLogger {
+
+@protected
+ NSUInteger _saveThreshold;
+ NSTimeInterval _saveInterval;
+ NSTimeInterval _maxAge;
+ NSTimeInterval _deleteInterval;
+ BOOL _deleteOnEverySave;
+
+ BOOL _saveTimerSuspended;
+ NSUInteger _unsavedCount;
+ dispatch_time_t _unsavedTime;
+ dispatch_source_t _saveTimer;
+ dispatch_time_t _lastDeleteTime;
+ dispatch_source_t _deleteTimer;
+}
+
+/**
+ * Specifies how often to save the data to disk.
+ * Since saving is an expensive operation (disk io) it is not done after every log statement.
+ * These properties allow you to configure how/when the logger saves to disk.
+ *
+ * A save is done when either (whichever happens first):
+ *
+ * - The number of unsaved log entries reaches saveThreshold
+ * - The amount of time since the oldest unsaved log entry was created reaches saveInterval
+ *
+ * You can optionally disable the saveThreshold by setting it to zero.
+ * If you disable the saveThreshold you are entirely dependent on the saveInterval.
+ *
+ * You can optionally disable the saveInterval by setting it to zero (or a negative value).
+ * If you disable the saveInterval you are entirely dependent on the saveThreshold.
+ *
+ * It's not wise to disable both saveThreshold and saveInterval.
+ *
+ * The default saveThreshold is 500.
+ * The default saveInterval is 60 seconds.
+ **/
+@property (assign, readwrite) NSUInteger saveThreshold;
+
+/**
+ * See the description for the `saveThreshold` property
+ */
+@property (assign, readwrite) NSTimeInterval saveInterval;
+
+/**
+ * It is likely you don't want the log entries to persist forever.
+ * Doing so would allow the database to grow infinitely large over time.
+ *
+ * The maxAge property provides a way to specify how old a log statement can get
+ * before it should get deleted from the database.
+ *
+ * The deleteInterval specifies how often to sweep for old log entries.
+ * Since deleting is an expensive operation (disk io) is is done on a fixed interval.
+ *
+ * An alternative to the deleteInterval is the deleteOnEverySave option.
+ * This specifies that old log entries should be deleted during every save operation.
+ *
+ * You can optionally disable the maxAge by setting it to zero (or a negative value).
+ * If you disable the maxAge then old log statements are not deleted.
+ *
+ * You can optionally disable the deleteInterval by setting it to zero (or a negative value).
+ *
+ * If you disable both deleteInterval and deleteOnEverySave then old log statements are not deleted.
+ *
+ * It's not wise to enable both deleteInterval and deleteOnEverySave.
+ *
+ * The default maxAge is 7 days.
+ * The default deleteInterval is 5 minutes.
+ * The default deleteOnEverySave is NO.
+ **/
+@property (assign, readwrite) NSTimeInterval maxAge;
+
+/**
+ * See the description for the `maxAge` property
+ */
+@property (assign, readwrite) NSTimeInterval deleteInterval;
+
+/**
+ * See the description for the `maxAge` property
+ */
+@property (assign, readwrite) BOOL deleteOnEverySave;
+
+/**
+ * Forces a save of any pending log entries (flushes log entries to disk).
+ **/
+- (void)savePendingLogEntries;
+
+/**
+ * Removes any log entries that are older than maxAge.
+ **/
+- (void)deleteOldLogEntries;
+
+@end
diff --git a/cocoalumberjack/Classes/DDAbstractDatabaseLogger.m b/cocoalumberjack/Classes/DDAbstractDatabaseLogger.m
new file mode 100644
index 0000000..c8782de
--- /dev/null
+++ b/cocoalumberjack/Classes/DDAbstractDatabaseLogger.m
@@ -0,0 +1,660 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import "DDAbstractDatabaseLogger.h"
+#import <math.h>
+
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+@interface DDAbstractDatabaseLogger ()
+
+- (void)destroySaveTimer;
+- (void)destroyDeleteTimer;
+
+@end
+
+#pragma mark -
+
+@implementation DDAbstractDatabaseLogger
+
+- (instancetype)init {
+ if ((self = [super init])) {
+ _saveThreshold = 500;
+ _saveInterval = 60; // 60 seconds
+ _maxAge = (60 * 60 * 24 * 7); // 7 days
+ _deleteInterval = (60 * 5); // 5 minutes
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ [self destroySaveTimer];
+ [self destroyDeleteTimer];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Override Me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)db_log:(DDLogMessage *)logMessage {
+ // Override me and add your implementation.
+ //
+ // Return YES if an item was added to the buffer.
+ // Return NO if the logMessage was ignored.
+
+ return NO;
+}
+
+- (void)db_save {
+ // Override me and add your implementation.
+}
+
+- (void)db_delete {
+ // Override me and add your implementation.
+}
+
+- (void)db_saveAndDelete {
+ // Override me and add your implementation.
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Private API
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)performSaveAndSuspendSaveTimer {
+ if (_unsavedCount > 0) {
+ if (_deleteOnEverySave) {
+ [self db_saveAndDelete];
+ } else {
+ [self db_save];
+ }
+ }
+
+ _unsavedCount = 0;
+ _unsavedTime = 0;
+
+ if (_saveTimer && !_saveTimerSuspended) {
+ dispatch_suspend(_saveTimer);
+ _saveTimerSuspended = YES;
+ }
+}
+
+- (void)performDelete {
+ if (_maxAge > 0.0) {
+ [self db_delete];
+
+ _lastDeleteTime = dispatch_time(DISPATCH_TIME_NOW, 0);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Timers
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)destroySaveTimer {
+ if (_saveTimer) {
+ dispatch_source_cancel(_saveTimer);
+
+ if (_saveTimerSuspended) {
+ // Must resume a timer before releasing it (or it will crash)
+ dispatch_resume(_saveTimer);
+ _saveTimerSuspended = NO;
+ }
+
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_release(_saveTimer);
+ #endif
+ _saveTimer = NULL;
+ }
+}
+
+- (void)updateAndResumeSaveTimer {
+ if ((_saveTimer != NULL) && (_saveInterval > 0.0) && (_unsavedTime > 0.0)) {
+ uint64_t interval = (uint64_t)(_saveInterval * NSEC_PER_SEC);
+ dispatch_time_t startTime = dispatch_time(_unsavedTime, interval);
+
+ dispatch_source_set_timer(_saveTimer, startTime, interval, 1.0);
+
+ if (_saveTimerSuspended) {
+ dispatch_resume(_saveTimer);
+ _saveTimerSuspended = NO;
+ }
+ }
+}
+
+- (void)createSuspendedSaveTimer {
+ if ((_saveTimer == NULL) && (_saveInterval > 0.0)) {
+ _saveTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue);
+
+ dispatch_source_set_event_handler(_saveTimer, ^{ @autoreleasepool {
+ [self performSaveAndSuspendSaveTimer];
+ } });
+
+ _saveTimerSuspended = YES;
+ }
+}
+
+- (void)destroyDeleteTimer {
+ if (_deleteTimer) {
+ dispatch_source_cancel(_deleteTimer);
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_release(_deleteTimer);
+ #endif
+ _deleteTimer = NULL;
+ }
+}
+
+- (void)updateDeleteTimer {
+ if ((_deleteTimer != NULL) && (_deleteInterval > 0.0) && (_maxAge > 0.0)) {
+ uint64_t interval = (uint64_t)(_deleteInterval * NSEC_PER_SEC);
+ dispatch_time_t startTime;
+
+ if (_lastDeleteTime > 0) {
+ startTime = dispatch_time(_lastDeleteTime, interval);
+ } else {
+ startTime = dispatch_time(DISPATCH_TIME_NOW, interval);
+ }
+
+ dispatch_source_set_timer(_deleteTimer, startTime, interval, 1.0);
+ }
+}
+
+- (void)createAndStartDeleteTimer {
+ if ((_deleteTimer == NULL) && (_deleteInterval > 0.0) && (_maxAge > 0.0)) {
+ _deleteTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue);
+
+ if (_deleteTimer != NULL) {
+ dispatch_source_set_event_handler(_deleteTimer, ^{ @autoreleasepool {
+ [self performDelete];
+ } });
+
+ [self updateDeleteTimer];
+
+ if (_deleteTimer != NULL) {
+ dispatch_resume(_deleteTimer);
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSUInteger)saveThreshold {
+ // The design of this method is taken from the DDAbstractLogger implementation.
+ // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+ // Note: The internal implementation MUST access the colorsEnabled variable directly,
+ // This method is designed explicitly for external access.
+ //
+ // Using "self." syntax to go through this method will cause immediate deadlock.
+ // This is the intended result. Fix it by accessing the ivar directly.
+ // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+ NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
+
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+
+ __block NSUInteger result;
+
+ dispatch_sync(globalLoggingQueue, ^{
+ dispatch_sync(self.loggerQueue, ^{
+ result = _saveThreshold;
+ });
+ });
+
+ return result;
+}
+
+- (void)setSaveThreshold:(NSUInteger)threshold {
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ if (_saveThreshold != threshold) {
+ _saveThreshold = threshold;
+
+ // Since the saveThreshold has changed,
+ // we check to see if the current unsavedCount has surpassed the new threshold.
+ //
+ // If it has, we immediately save the log.
+
+ if ((_unsavedCount >= _saveThreshold) && (_saveThreshold > 0)) {
+ [self performSaveAndSuspendSaveTimer];
+ }
+ }
+ }
+ };
+
+ // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+ // For documentation please refer to the DDAbstractLogger implementation.
+
+ if ([self isOnInternalLoggerQueue]) {
+ block();
+ } else {
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+ }
+}
+
+- (NSTimeInterval)saveInterval {
+ // The design of this method is taken from the DDAbstractLogger implementation.
+ // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+ // Note: The internal implementation MUST access the colorsEnabled variable directly,
+ // This method is designed explicitly for external access.
+ //
+ // Using "self." syntax to go through this method will cause immediate deadlock.
+ // This is the intended result. Fix it by accessing the ivar directly.
+ // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+ NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
+
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+
+ __block NSTimeInterval result;
+
+ dispatch_sync(globalLoggingQueue, ^{
+ dispatch_sync(self.loggerQueue, ^{
+ result = _saveInterval;
+ });
+ });
+
+ return result;
+}
+
+- (void)setSaveInterval:(NSTimeInterval)interval {
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ // C99 recommended floating point comparison macro
+ // Read: isLessThanOrGreaterThan(floatA, floatB)
+
+ if (/* saveInterval != interval */ islessgreater(_saveInterval, interval)) {
+ _saveInterval = interval;
+
+ // There are several cases we need to handle here.
+ //
+ // 1. If the saveInterval was previously enabled and it just got disabled,
+ // then we need to stop the saveTimer. (And we might as well release it.)
+ //
+ // 2. If the saveInterval was previously disabled and it just got enabled,
+ // then we need to setup the saveTimer. (Plus we might need to do an immediate save.)
+ //
+ // 3. If the saveInterval increased, then we need to reset the timer so that it fires at the later date.
+ //
+ // 4. If the saveInterval decreased, then we need to reset the timer so that it fires at an earlier date.
+ // (Plus we might need to do an immediate save.)
+
+ if (_saveInterval > 0.0) {
+ if (_saveTimer == NULL) {
+ // Handles #2
+ //
+ // Since the saveTimer uses the unsavedTime to calculate it's first fireDate,
+ // if a save is needed the timer will fire immediately.
+
+ [self createSuspendedSaveTimer];
+ [self updateAndResumeSaveTimer];
+ } else {
+ // Handles #3
+ // Handles #4
+ //
+ // Since the saveTimer uses the unsavedTime to calculate it's first fireDate,
+ // if a save is needed the timer will fire immediately.
+
+ [self updateAndResumeSaveTimer];
+ }
+ } else if (_saveTimer) {
+ // Handles #1
+
+ [self destroySaveTimer];
+ }
+ }
+ }
+ };
+
+ // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+ // For documentation please refer to the DDAbstractLogger implementation.
+
+ if ([self isOnInternalLoggerQueue]) {
+ block();
+ } else {
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+ }
+}
+
+- (NSTimeInterval)maxAge {
+ // The design of this method is taken from the DDAbstractLogger implementation.
+ // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+ // Note: The internal implementation MUST access the colorsEnabled variable directly,
+ // This method is designed explicitly for external access.
+ //
+ // Using "self." syntax to go through this method will cause immediate deadlock.
+ // This is the intended result. Fix it by accessing the ivar directly.
+ // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+ NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
+
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+
+ __block NSTimeInterval result;
+
+ dispatch_sync(globalLoggingQueue, ^{
+ dispatch_sync(self.loggerQueue, ^{
+ result = _maxAge;
+ });
+ });
+
+ return result;
+}
+
+- (void)setMaxAge:(NSTimeInterval)interval {
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ // C99 recommended floating point comparison macro
+ // Read: isLessThanOrGreaterThan(floatA, floatB)
+
+ if (/* maxAge != interval */ islessgreater(_maxAge, interval)) {
+ NSTimeInterval oldMaxAge = _maxAge;
+ NSTimeInterval newMaxAge = interval;
+
+ _maxAge = interval;
+
+ // There are several cases we need to handle here.
+ //
+ // 1. If the maxAge was previously enabled and it just got disabled,
+ // then we need to stop the deleteTimer. (And we might as well release it.)
+ //
+ // 2. If the maxAge was previously disabled and it just got enabled,
+ // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.)
+ //
+ // 3. If the maxAge was increased,
+ // then we don't need to do anything.
+ //
+ // 4. If the maxAge was decreased,
+ // then we should do an immediate delete.
+
+ BOOL shouldDeleteNow = NO;
+
+ if (oldMaxAge > 0.0) {
+ if (newMaxAge <= 0.0) {
+ // Handles #1
+
+ [self destroyDeleteTimer];
+ } else if (oldMaxAge > newMaxAge) {
+ // Handles #4
+ shouldDeleteNow = YES;
+ }
+ } else if (newMaxAge > 0.0) {
+ // Handles #2
+ shouldDeleteNow = YES;
+ }
+
+ if (shouldDeleteNow) {
+ [self performDelete];
+
+ if (_deleteTimer) {
+ [self updateDeleteTimer];
+ } else {
+ [self createAndStartDeleteTimer];
+ }
+ }
+ }
+ }
+ };
+
+ // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+ // For documentation please refer to the DDAbstractLogger implementation.
+
+ if ([self isOnInternalLoggerQueue]) {
+ block();
+ } else {
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+ }
+}
+
+- (NSTimeInterval)deleteInterval {
+ // The design of this method is taken from the DDAbstractLogger implementation.
+ // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+ // Note: The internal implementation MUST access the colorsEnabled variable directly,
+ // This method is designed explicitly for external access.
+ //
+ // Using "self." syntax to go through this method will cause immediate deadlock.
+ // This is the intended result. Fix it by accessing the ivar directly.
+ // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+ NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
+
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+
+ __block NSTimeInterval result;
+
+ dispatch_sync(globalLoggingQueue, ^{
+ dispatch_sync(self.loggerQueue, ^{
+ result = _deleteInterval;
+ });
+ });
+
+ return result;
+}
+
+- (void)setDeleteInterval:(NSTimeInterval)interval {
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ // C99 recommended floating point comparison macro
+ // Read: isLessThanOrGreaterThan(floatA, floatB)
+
+ if (/* deleteInterval != interval */ islessgreater(_deleteInterval, interval)) {
+ _deleteInterval = interval;
+
+ // There are several cases we need to handle here.
+ //
+ // 1. If the deleteInterval was previously enabled and it just got disabled,
+ // then we need to stop the deleteTimer. (And we might as well release it.)
+ //
+ // 2. If the deleteInterval was previously disabled and it just got enabled,
+ // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.)
+ //
+ // 3. If the deleteInterval increased, then we need to reset the timer so that it fires at the later date.
+ //
+ // 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date.
+ // (Plus we might need to do an immediate delete.)
+
+ if (_deleteInterval > 0.0) {
+ if (_deleteTimer == NULL) {
+ // Handles #2
+ //
+ // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate,
+ // if a delete is needed the timer will fire immediately.
+
+ [self createAndStartDeleteTimer];
+ } else {
+ // Handles #3
+ // Handles #4
+ //
+ // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate,
+ // if a save is needed the timer will fire immediately.
+
+ [self updateDeleteTimer];
+ }
+ } else if (_deleteTimer) {
+ // Handles #1
+
+ [self destroyDeleteTimer];
+ }
+ }
+ }
+ };
+
+ // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+ // For documentation please refer to the DDAbstractLogger implementation.
+
+ if ([self isOnInternalLoggerQueue]) {
+ block();
+ } else {
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+ }
+}
+
+- (BOOL)deleteOnEverySave {
+ // The design of this method is taken from the DDAbstractLogger implementation.
+ // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+ // Note: The internal implementation MUST access the colorsEnabled variable directly,
+ // This method is designed explicitly for external access.
+ //
+ // Using "self." syntax to go through this method will cause immediate deadlock.
+ // This is the intended result. Fix it by accessing the ivar directly.
+ // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+ NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
+
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+
+ __block BOOL result;
+
+ dispatch_sync(globalLoggingQueue, ^{
+ dispatch_sync(self.loggerQueue, ^{
+ result = _deleteOnEverySave;
+ });
+ });
+
+ return result;
+}
+
+- (void)setDeleteOnEverySave:(BOOL)flag {
+ dispatch_block_t block = ^{
+ _deleteOnEverySave = flag;
+ };
+
+ // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+ // For documentation please refer to the DDAbstractLogger implementation.
+
+ if ([self isOnInternalLoggerQueue]) {
+ block();
+ } else {
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Public API
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)savePendingLogEntries {
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ [self performSaveAndSuspendSaveTimer];
+ }
+ };
+
+ if ([self isOnInternalLoggerQueue]) {
+ block();
+ } else {
+ dispatch_async(self.loggerQueue, block);
+ }
+}
+
+- (void)deleteOldLogEntries {
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ [self performDelete];
+ }
+ };
+
+ if ([self isOnInternalLoggerQueue]) {
+ block();
+ } else {
+ dispatch_async(self.loggerQueue, block);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark DDLogger
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)didAddLogger {
+ // If you override me be sure to invoke [super didAddLogger];
+
+ [self createSuspendedSaveTimer];
+
+ [self createAndStartDeleteTimer];
+}
+
+- (void)willRemoveLogger {
+ // If you override me be sure to invoke [super willRemoveLogger];
+
+ [self performSaveAndSuspendSaveTimer];
+
+ [self destroySaveTimer];
+ [self destroyDeleteTimer];
+}
+
+- (void)logMessage:(DDLogMessage *)logMessage {
+ if ([self db_log:logMessage]) {
+ BOOL firstUnsavedEntry = (++_unsavedCount == 1);
+
+ if ((_unsavedCount >= _saveThreshold) && (_saveThreshold > 0)) {
+ [self performSaveAndSuspendSaveTimer];
+ } else if (firstUnsavedEntry) {
+ _unsavedTime = dispatch_time(DISPATCH_TIME_NOW, 0);
+ [self updateAndResumeSaveTimer];
+ }
+ }
+}
+
+- (void)flush {
+ // This method is invoked by DDLog's flushLog method.
+ //
+ // It is called automatically when the application quits,
+ // or if the developer invokes DDLog's flushLog method prior to crashing or something.
+
+ [self performSaveAndSuspendSaveTimer];
+}
+
+@end
diff --git a/cocoalumberjack/Classes/DDAssertMacros.h b/cocoalumberjack/Classes/DDAssertMacros.h
new file mode 100644
index 0000000..870d31f
--- /dev/null
+++ b/cocoalumberjack/Classes/DDAssertMacros.h
@@ -0,0 +1,26 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+/**
+ * NSAsset replacement that will output a log message even when assertions are disabled.
+ **/
+#define DDAssert(condition, frmt, ...) \
+ if (!(condition)) { \
+ NSString *description = [NSString stringWithFormat:frmt, ## __VA_ARGS__]; \
+ DDLogError(@"%@", description); \
+ NSAssert(NO, description); \
+ }
+#define DDAssertCondition(condition) DDAssert(condition, @"Condition not satisfied: %s", #condition)
+
diff --git a/cocoalumberjack/Classes/DDFileLogger.h b/cocoalumberjack/Classes/DDFileLogger.h
new file mode 100644
index 0000000..f0bfdb6
--- /dev/null
+++ b/cocoalumberjack/Classes/DDFileLogger.h
@@ -0,0 +1,487 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+ #define DD_LEGACY_MACROS 0
+#endif
+
+#import "DDLog.h"
+
+@class DDLogFileInfo;
+
+/**
+ * This class provides a logger to write log statements to a file.
+ **/
+
+
+// Default configuration and safety/sanity values.
+//
+// maximumFileSize -> kDDDefaultLogMaxFileSize
+// rollingFrequency -> kDDDefaultLogRollingFrequency
+// maximumNumberOfLogFiles -> kDDDefaultLogMaxNumLogFiles
+// logFilesDiskQuota -> kDDDefaultLogFilesDiskQuota
+//
+// You should carefully consider the proper configuration values for your application.
+
+extern unsigned long long const kDDDefaultLogMaxFileSize;
+extern NSTimeInterval const kDDDefaultLogRollingFrequency;
+extern NSUInteger const kDDDefaultLogMaxNumLogFiles;
+extern unsigned long long const kDDDefaultLogFilesDiskQuota;
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The LogFileManager protocol is designed to allow you to control all aspects of your log files.
+ *
+ * The primary purpose of this is to allow you to do something with the log files after they have been rolled.
+ * Perhaps you want to compress them to save disk space.
+ * Perhaps you want to upload them to an FTP server.
+ * Perhaps you want to run some analytics on the file.
+ *
+ * A default LogFileManager is, of course, provided.
+ * The default LogFileManager simply deletes old log files according to the maximumNumberOfLogFiles property.
+ *
+ * This protocol provides various methods to fetch the list of log files.
+ *
+ * There are two variants: sorted and unsorted.
+ * If sorting is not necessary, the unsorted variant is obviously faster.
+ * The sorted variant will return an array sorted by when the log files were created,
+ * with the most recently created log file at index 0, and the oldest log file at the end of the array.
+ *
+ * You can fetch only the log file paths (full path including name), log file names (name only),
+ * or an array of `DDLogFileInfo` objects.
+ * The `DDLogFileInfo` class is documented below, and provides a handy wrapper that
+ * gives you easy access to various file attributes such as the creation date or the file size.
+ */
+@protocol DDLogFileManager <NSObject>
+@required
+
+// Public properties
+
+/**
+ * The maximum number of archived log files to keep on disk.
+ * For example, if this property is set to 3,
+ * then the LogFileManager will only keep 3 archived log files (plus the current active log file) on disk.
+ * Once the active log file is rolled/archived, then the oldest of the existing 3 rolled/archived log files is deleted.
+ *
+ * You may optionally disable this option by setting it to zero.
+ **/
+@property (readwrite, assign, atomic) NSUInteger maximumNumberOfLogFiles;
+
+/**
+ * The maximum space that logs can take. On rolling logfile all old logfiles that exceed logFilesDiskQuota will
+ * be deleted.
+ *
+ * You may optionally disable this option by setting it to zero.
+ **/
+@property (readwrite, assign, atomic) unsigned long long logFilesDiskQuota;
+
+// Public methods
+
+/**
+ * Returns the logs directory (path)
+ */
+- (NSString *)logsDirectory;
+
+/**
+ * Returns an array of `NSString` objects,
+ * each of which is the filePath to an existing log file on disk.
+ **/
+- (NSArray *)unsortedLogFilePaths;
+
+/**
+ * Returns an array of `NSString` objects,
+ * each of which is the fileName of an existing log file on disk.
+ **/
+- (NSArray *)unsortedLogFileNames;
+
+/**
+ * Returns an array of `DDLogFileInfo` objects,
+ * each representing an existing log file on disk,
+ * and containing important information about the log file such as it's modification date and size.
+ **/
+- (NSArray *)unsortedLogFileInfos;
+
+/**
+ * Just like the `unsortedLogFilePaths` method, but sorts the array.
+ * The items in the array are sorted by creation date.
+ * The first item in the array will be the most recently created log file.
+ **/
+- (NSArray *)sortedLogFilePaths;
+
+/**
+ * Just like the `unsortedLogFileNames` method, but sorts the array.
+ * The items in the array are sorted by creation date.
+ * The first item in the array will be the most recently created log file.
+ **/
+- (NSArray *)sortedLogFileNames;
+
+/**
+ * Just like the `unsortedLogFileInfos` method, but sorts the array.
+ * The items in the array are sorted by creation date.
+ * The first item in the array will be the most recently created log file.
+ **/
+- (NSArray *)sortedLogFileInfos;
+
+// Private methods (only to be used by DDFileLogger)
+
+/**
+ * Generates a new unique log file path, and creates the corresponding log file.
+ **/
+- (NSString *)createNewLogFile;
+
+@optional
+
+// Notifications from DDFileLogger
+
+/**
+ * Called when a log file was archieved
+ */
+- (void)didArchiveLogFile:(NSString *)logFilePath;
+
+/**
+ * Called when the roll action was executed and the log was archieved
+ */
+- (void)didRollAndArchiveLogFile:(NSString *)logFilePath;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Default log file manager.
+ *
+ * All log files are placed inside the logsDirectory.
+ * If a specific logsDirectory isn't specified, the default directory is used.
+ * On Mac, this is in `~/Library/Logs/<Application Name>`.
+ * On iPhone, this is in `~/Library/Caches/Logs`.
+ *
+ * Log files are named `"<bundle identifier> <date> <time>.log"`
+ * Example: `com.organization.myapp 2013-12-03 17-14.log`
+ *
+ * Archived log files are automatically deleted according to the `maximumNumberOfLogFiles` property.
+ **/
+@interface DDLogFileManagerDefault : NSObject <DDLogFileManager>
+
+/**
+ * Default initializer
+ */
+- (instancetype)init;
+
+/**
+ * Designated initialized, requires the logs directory
+ */
+- (instancetype)initWithLogsDirectory:(NSString *)logsDirectory NS_DESIGNATED_INITIALIZER;
+
+#if TARGET_OS_IPHONE
+/*
+ * Calling this constructor you can override the default "automagically" chosen NSFileProtection level.
+ * Useful if you are writing a command line utility / CydiaSubstrate addon for iOS that has no NSBundle
+ * or like SpringBoard no BackgroundModes key in the NSBundle:
+ * iPhone:~ root# cycript -p SpringBoard
+ * cy# [NSBundle mainBundle]
+ * #"NSBundle </System/Library/CoreServices/SpringBoard.app> (loaded)"
+ * cy# [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
+ * null
+ * cy#
+ **/
+- (instancetype)initWithLogsDirectory:(NSString *)logsDirectory defaultFileProtectionLevel:(NSString *)fileProtectionLevel;
+#endif
+
+/*
+ * Methods to override.
+ *
+ * Log files are named `"<bundle identifier> <date> <time>.log"`
+ * Example: `com.organization.myapp 2013-12-03 17-14.log`
+ *
+ * If you wish to change default filename, you can override following two methods.
+ * - `newLogFileName` method would be called on new logfile creation.
+ * - `isLogFile:` method would be called to filter logfiles from all other files in logsDirectory.
+ * You have to parse given filename and return YES if it is logFile.
+ *
+ * **NOTE**
+ * `newLogFileName` returns filename. If appropriate file already exists, number would be added
+ * to filename before extension. You have to handle this case in isLogFile: method.
+ *
+ * Example:
+ * - newLogFileName returns `"com.organization.myapp 2013-12-03.log"`,
+ * file `"com.organization.myapp 2013-12-03.log"` would be created.
+ * - after some time `"com.organization.myapp 2013-12-03.log"` is archived
+ * - newLogFileName again returns `"com.organization.myapp 2013-12-03.log"`,
+ * file `"com.organization.myapp 2013-12-03 2.log"` would be created.
+ * - after some time `"com.organization.myapp 2013-12-03 1.log"` is archived
+ * - newLogFileName again returns `"com.organization.myapp 2013-12-03.log"`,
+ * file `"com.organization.myapp 2013-12-03 3.log"` would be created.
+ **/
+
+/**
+ * Generates log file name with default format `"<bundle identifier> <date> <time>.log"`
+ * Example: `MobileSafari 2013-12-03 17-14.log`
+ *
+ * You can change it by overriding `newLogFileName` and `isLogFile:` methods.
+ **/
+@property (readonly, copy) NSString *newLogFileName;
+
+/**
+ * Default log file name is `"<bundle identifier> <date> <time>.log"`.
+ * Example: `MobileSafari 2013-12-03 17-14.log`
+ *
+ * You can change it by overriding `newLogFileName` and `isLogFile:` methods.
+ **/
+- (BOOL)isLogFile:(NSString *)fileName;
+
+/* Inherited from DDLogFileManager protocol:
+
+ @property (readwrite, assign, atomic) NSUInteger maximumNumberOfLogFiles;
+ @property (readwrite, assign, atomic) NSUInteger logFilesDiskQuota;
+
+ - (NSString *)logsDirectory;
+
+ - (NSArray *)unsortedLogFilePaths;
+ - (NSArray *)unsortedLogFileNames;
+ - (NSArray *)unsortedLogFileInfos;
+
+ - (NSArray *)sortedLogFilePaths;
+ - (NSArray *)sortedLogFileNames;
+ - (NSArray *)sortedLogFileInfos;
+
+ */
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Most users will want file log messages to be prepended with the date and time.
+ * Rather than forcing the majority of users to write their own formatter,
+ * we will supply a logical default formatter.
+ * Users can easily replace this formatter with their own by invoking the `setLogFormatter:` method.
+ * It can also be removed by calling `setLogFormatter:`, and passing a nil parameter.
+ *
+ * In addition to the convenience of having a logical default formatter,
+ * it will also provide a template that makes it easy for developers to copy and change.
+ **/
+@interface DDLogFileFormatterDefault : NSObject <DDLogFormatter>
+
+/**
+ * Default initializer
+ */
+- (instancetype)init;
+
+/**
+ * Designated initializer, requires a date formatter
+ */
+- (instancetype)initWithDateFormatter:(NSDateFormatter *)dateFormatter NS_DESIGNATED_INITIALIZER;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The standard implementation for a file logger
+ */
+@interface DDFileLogger : DDAbstractLogger <DDLogger>
+
+/**
+ * Default initializer
+ */
+- (instancetype)init;
+
+/**
+ * Designated initializer, requires a `DDLogFileManager` instance
+ */
+- (instancetype)initWithLogFileManager:(id <DDLogFileManager>)logFileManager NS_DESIGNATED_INITIALIZER;
+
+/**
+ * Log File Rolling:
+ *
+ * `maximumFileSize`:
+ * The approximate maximum size to allow log files to grow.
+ * If a log file is larger than this value after a log statement is appended,
+ * then the log file is rolled.
+ *
+ * `rollingFrequency`
+ * How often to roll the log file.
+ * The frequency is given as an `NSTimeInterval`, which is a double that specifies the interval in seconds.
+ * Once the log file gets to be this old, it is rolled.
+ *
+ * Both the `maximumFileSize` and the `rollingFrequency` are used to manage rolling.
+ * Whichever occurs first will cause the log file to be rolled.
+ *
+ * For example:
+ * The `rollingFrequency` is 24 hours,
+ * but the log file surpasses the `maximumFileSize` after only 20 hours.
+ * The log file will be rolled at that 20 hour mark.
+ * A new log file will be created, and the 24 hour timer will be restarted.
+ *
+ * You may optionally disable rolling due to filesize by setting `maximumFileSize` to zero.
+ * If you do so, rolling is based solely on `rollingFrequency`.
+ *
+ * You may optionally disable rolling due to time by setting `rollingFrequency` to zero (or any non-positive number).
+ * If you do so, rolling is based solely on `maximumFileSize`.
+ *
+ * If you disable both `maximumFileSize` and `rollingFrequency`, then the log file won't ever be rolled.
+ * This is strongly discouraged.
+ **/
+@property (readwrite, assign) unsigned long long maximumFileSize;
+
+/**
+ * See description for `maximumFileSize`
+ */
+@property (readwrite, assign) NSTimeInterval rollingFrequency;
+
+/**
+ * See description for `maximumFileSize`
+ */
+@property (readwrite, assign, atomic) BOOL doNotReuseLogFiles;
+
+/**
+ * The DDLogFileManager instance can be used to retrieve the list of log files,
+ * and configure the maximum number of archived log files to keep.
+ *
+ * @see DDLogFileManager.maximumNumberOfLogFiles
+ **/
+@property (strong, nonatomic, readonly) id <DDLogFileManager> logFileManager;
+
+/**
+ * When using a custom formatter you can set the `logMessage` method not to append
+ * `\n` character after each output. This allows for some greater flexibility with
+ * custom formatters. Default value is YES.
+ **/
+@property (nonatomic, readwrite, assign) BOOL automaticallyAppendNewlineForCustomFormatters;
+
+/**
+ * You can optionally force the current log file to be rolled with this method.
+ * CompletionBlock will be called on main queue.
+ */
+- (void)rollLogFileWithCompletionBlock:(void (^)())completionBlock;
+
+/**
+ * Method is deprecated.
+ * @deprecated Use `rollLogFileWithCompletionBlock:` method instead.
+ */
+- (void)rollLogFile __attribute((deprecated));
+
+// Inherited from DDAbstractLogger
+
+// - (id <DDLogFormatter>)logFormatter;
+// - (void)setLogFormatter:(id <DDLogFormatter>)formatter;
+
+/**
+ * Returns the log file that should be used.
+ * If there is an existing log file that is suitable,
+ * within the constraints of `maximumFileSize` and `rollingFrequency`, then it is returned.
+ *
+ * Otherwise a new file is created and returned.
+ **/
+- (DDLogFileInfo *)currentLogFileInfo;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * `DDLogFileInfo` is a simple class that provides access to various file attributes.
+ * It provides good performance as it only fetches the information if requested,
+ * and it caches the information to prevent duplicate fetches.
+ *
+ * It was designed to provide quick snapshots of the current state of log files,
+ * and to help sort log files in an array.
+ *
+ * This class does not monitor the files, or update it's cached attribute values if the file changes on disk.
+ * This is not what the class was designed for.
+ *
+ * If you absolutely must get updated values,
+ * you can invoke the reset method which will clear the cache.
+ **/
+@interface DDLogFileInfo : NSObject
+
+@property (strong, nonatomic, readonly) NSString *filePath;
+@property (strong, nonatomic, readonly) NSString *fileName;
+
+@property (strong, nonatomic, readonly) NSDictionary *fileAttributes;
+
+@property (strong, nonatomic, readonly) NSDate *creationDate;
+@property (strong, nonatomic, readonly) NSDate *modificationDate;
+
+@property (nonatomic, readonly) unsigned long long fileSize;
+
+@property (nonatomic, readonly) NSTimeInterval age;
+
+@property (nonatomic, readwrite) BOOL isArchived;
+
++ (instancetype)logFileWithPath:(NSString *)filePath;
+
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initWithFilePath:(NSString *)filePath NS_DESIGNATED_INITIALIZER;
+
+- (void)reset;
+- (void)renameFile:(NSString *)newFileName;
+
+#if TARGET_IPHONE_SIMULATOR
+
+// So here's the situation.
+// Extended attributes are perfect for what we're trying to do here (marking files as archived).
+// This is exactly what extended attributes were designed for.
+//
+// But Apple screws us over on the simulator.
+// Everytime you build-and-go, they copy the application into a new folder on the hard drive,
+// and as part of the process they strip extended attributes from our log files.
+// Normally, a copy of a file preserves extended attributes.
+// So obviously Apple has gone to great lengths to piss us off.
+//
+// Thus we use a slightly different tactic for marking log files as archived in the simulator.
+// That way it "just works" and there's no confusion when testing.
+//
+// The difference in method names is indicative of the difference in functionality.
+// On the simulator we add an attribute by appending a filename extension.
+//
+// For example:
+// "mylog.txt" -> "mylog.archived.txt"
+// "mylog" -> "mylog.archived"
+
+- (BOOL)hasExtensionAttributeWithName:(NSString *)attrName;
+
+- (void)addExtensionAttributeWithName:(NSString *)attrName;
+- (void)removeExtensionAttributeWithName:(NSString *)attrName;
+
+#else /* if TARGET_IPHONE_SIMULATOR */
+
+// Normal use of extended attributes used everywhere else,
+// such as on Macs and on iPhone devices.
+
+- (BOOL)hasExtendedAttributeWithName:(NSString *)attrName;
+
+- (void)addExtendedAttributeWithName:(NSString *)attrName;
+- (void)removeExtendedAttributeWithName:(NSString *)attrName;
+
+#endif /* if TARGET_IPHONE_SIMULATOR */
+
+- (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another;
+- (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another;
+
+@end
diff --git a/cocoalumberjack/Classes/DDFileLogger.m b/cocoalumberjack/Classes/DDFileLogger.m
new file mode 100644
index 0000000..1b78578
--- /dev/null
+++ b/cocoalumberjack/Classes/DDFileLogger.m
@@ -0,0 +1,1484 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import "DDFileLogger.h"
+
+#import <unistd.h>
+#import <sys/attr.h>
+#import <sys/xattr.h>
+#import <libkern/OSAtomic.h>
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// We probably shouldn't be using DDLog() statements within the DDLog implementation.
+// But we still want to leave our log statements for any future debugging,
+// and to allow other developers to trace the implementation (which is a great learning tool).
+//
+// So we use primitive logging macros around NSLog.
+// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog.
+
+#ifndef DD_NSLOG_LEVEL
+ #define DD_NSLOG_LEVEL 2
+#endif
+
+#define NSLogError(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogWarn(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogInfo(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogDebug(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogVerbose(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 5) NSLog((frmt), ##__VA_ARGS__); } while(0)
+
+
+#if TARGET_OS_IPHONE
+BOOL doesAppRunInBackground(void);
+#endif
+
+unsigned long long const kDDDefaultLogMaxFileSize = 1024 * 1024; // 1 MB
+NSTimeInterval const kDDDefaultLogRollingFrequency = 60 * 60 * 24; // 24 Hours
+NSUInteger const kDDDefaultLogMaxNumLogFiles = 5; // 5 Files
+unsigned long long const kDDDefaultLogFilesDiskQuota = 20 * 1024 * 1024; // 20 MB
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface DDLogFileManagerDefault () {
+ NSUInteger _maximumNumberOfLogFiles;
+ unsigned long long _logFilesDiskQuota;
+ NSString *_logsDirectory;
+#if TARGET_OS_IPHONE
+ NSString *_defaultFileProtectionLevel;
+#endif
+}
+
+- (void)deleteOldLogFiles;
+- (NSString *)defaultLogsDirectory;
+
+@end
+
+@implementation DDLogFileManagerDefault
+
+@synthesize maximumNumberOfLogFiles = _maximumNumberOfLogFiles;
+@synthesize logFilesDiskQuota = _logFilesDiskQuota;
+
+
+- (instancetype)init {
+ return [self initWithLogsDirectory:nil];
+}
+
+- (instancetype)initWithLogsDirectory:(NSString *)aLogsDirectory {
+ if ((self = [super init])) {
+ _maximumNumberOfLogFiles = kDDDefaultLogMaxNumLogFiles;
+ _logFilesDiskQuota = kDDDefaultLogFilesDiskQuota;
+
+ if (aLogsDirectory) {
+ _logsDirectory = [aLogsDirectory copy];
+ } else {
+ _logsDirectory = [[self defaultLogsDirectory] copy];
+ }
+
+ NSKeyValueObservingOptions kvoOptions = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
+
+ [self addObserver:self forKeyPath:NSStringFromSelector(@selector(maximumNumberOfLogFiles)) options:kvoOptions context:nil];
+ [self addObserver:self forKeyPath:NSStringFromSelector(@selector(logFilesDiskQuota)) options:kvoOptions context:nil];
+
+ NSLogVerbose(@"DDFileLogManagerDefault: logsDirectory:\n%@", [self logsDirectory]);
+ NSLogVerbose(@"DDFileLogManagerDefault: sortedLogFileNames:\n%@", [self sortedLogFileNames]);
+ }
+
+ return self;
+}
+
++ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey
+{
+ BOOL automatic = NO;
+ if ([theKey isEqualToString:@"maximumNumberOfLogFiles"] || [theKey isEqualToString:@"logFilesDiskQuota"]) {
+ automatic = NO;
+ } else {
+ automatic = [super automaticallyNotifiesObserversForKey:theKey];
+ }
+
+ return automatic;
+}
+
+#if TARGET_OS_IPHONE
+- (instancetype)initWithLogsDirectory:(NSString *)logsDirectory defaultFileProtectionLevel:(NSString *)fileProtectionLevel {
+ if ((self = [self initWithLogsDirectory:logsDirectory])) {
+ if ([fileProtectionLevel isEqualToString:NSFileProtectionNone] ||
+ [fileProtectionLevel isEqualToString:NSFileProtectionComplete] ||
+ [fileProtectionLevel isEqualToString:NSFileProtectionCompleteUnlessOpen] ||
+ [fileProtectionLevel isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication]) {
+ _defaultFileProtectionLevel = fileProtectionLevel;
+ }
+ }
+
+ return self;
+}
+
+#endif
+
+- (void)dealloc {
+ // try-catch because the observer might be removed or never added. In this case, removeObserver throws and exception
+ @try {
+ [self removeObserver:self forKeyPath:NSStringFromSelector(@selector(maximumNumberOfLogFiles))];
+ [self removeObserver:self forKeyPath:NSStringFromSelector(@selector(logFilesDiskQuota))];
+ } @catch (NSException *exception) {
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context {
+ NSNumber *old = change[NSKeyValueChangeOldKey];
+ NSNumber *new = change[NSKeyValueChangeNewKey];
+
+ if ([old isEqual:new]) {
+ // No change in value - don't bother with any processing.
+ return;
+ }
+
+ if ([keyPath isEqualToString:NSStringFromSelector(@selector(maximumNumberOfLogFiles))] ||
+ [keyPath isEqualToString:NSStringFromSelector(@selector(logFilesDiskQuota))]) {
+ NSLogInfo(@"DDFileLogManagerDefault: Responding to configuration change: %@", keyPath);
+
+ dispatch_async([DDLog loggingQueue], ^{ @autoreleasepool {
+ [self deleteOldLogFiles];
+ } });
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark File Deleting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Deletes archived log files that exceed the maximumNumberOfLogFiles or logFilesDiskQuota configuration values.
+ **/
+- (void)deleteOldLogFiles {
+ NSLogVerbose(@"DDLogFileManagerDefault: deleteOldLogFiles");
+
+ NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
+
+ NSUInteger firstIndexToDelete = NSNotFound;
+
+ const unsigned long long diskQuota = self.logFilesDiskQuota;
+ const NSUInteger maxNumLogFiles = self.maximumNumberOfLogFiles;
+
+ if (diskQuota) {
+ unsigned long long used = 0;
+
+ for (NSUInteger i = 0; i < sortedLogFileInfos.count; i++) {
+ DDLogFileInfo *info = sortedLogFileInfos[i];
+ used += info.fileSize;
+
+ if (used > diskQuota) {
+ firstIndexToDelete = i;
+ break;
+ }
+ }
+ }
+
+ if (maxNumLogFiles) {
+ if (firstIndexToDelete == NSNotFound) {
+ firstIndexToDelete = maxNumLogFiles;
+ } else {
+ firstIndexToDelete = MIN(firstIndexToDelete, maxNumLogFiles);
+ }
+ }
+
+ if (firstIndexToDelete == 0) {
+ // Do we consider the first file?
+ // We are only supposed to be deleting archived files.
+ // In most cases, the first file is likely the log file that is currently being written to.
+ // So in most cases, we do not want to consider this file for deletion.
+
+ if (sortedLogFileInfos.count > 0) {
+ DDLogFileInfo *logFileInfo = sortedLogFileInfos[0];
+
+ if (!logFileInfo.isArchived) {
+ // Don't delete active file.
+ ++firstIndexToDelete;
+ }
+ }
+ }
+
+ if (firstIndexToDelete != NSNotFound) {
+ // removing all logfiles starting with firstIndexToDelete
+
+ for (NSUInteger i = firstIndexToDelete; i < sortedLogFileInfos.count; i++) {
+ DDLogFileInfo *logFileInfo = sortedLogFileInfos[i];
+
+ NSLogInfo(@"DDLogFileManagerDefault: Deleting file: %@", logFileInfo.fileName);
+
+ [[NSFileManager defaultManager] removeItemAtPath:logFileInfo.filePath error:nil];
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Log Files
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns the path to the default logs directory.
+ * If the logs directory doesn't exist, this method automatically creates it.
+ **/
+- (NSString *)defaultLogsDirectory {
+#if TARGET_OS_IPHONE
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
+ NSString *baseDir = paths.firstObject;
+ NSString *logsDirectory = [baseDir stringByAppendingPathComponent:@"Logs"];
+
+#else
+ NSString *appName = [[NSProcessInfo processInfo] processName];
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
+ NSString *basePath = ([paths count] > 0) ? paths[0] : NSTemporaryDirectory();
+ NSString *logsDirectory = [[basePath stringByAppendingPathComponent:@"Logs"] stringByAppendingPathComponent:appName];
+
+#endif
+
+ return logsDirectory;
+}
+
+- (NSString *)logsDirectory {
+ // We could do this check once, during initalization, and not bother again.
+ // But this way the code continues to work if the directory gets deleted while the code is running.
+
+ if (![[NSFileManager defaultManager] fileExistsAtPath:_logsDirectory]) {
+ NSError *err = nil;
+
+ if (![[NSFileManager defaultManager] createDirectoryAtPath:_logsDirectory
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:&err]) {
+ NSLogError(@"DDFileLogManagerDefault: Error creating logsDirectory: %@", err);
+ }
+ }
+
+ return _logsDirectory;
+}
+
+- (BOOL)isLogFile:(NSString *)fileName {
+ NSString *appName = [self applicationName];
+
+ BOOL hasProperPrefix = [fileName hasPrefix:appName];
+ BOOL hasProperSuffix = [fileName hasSuffix:@".log"];
+ BOOL hasProperDate = NO;
+
+ if (hasProperPrefix && hasProperSuffix) {
+ NSUInteger lengthOfMiddle = fileName.length - appName.length - @".log".length;
+
+ // Date string should have at least 16 characters - " 2013-12-03 17-14"
+ if (lengthOfMiddle >= 17) {
+ NSRange range = NSMakeRange(appName.length, lengthOfMiddle);
+
+ NSString *middle = [fileName substringWithRange:range];
+ NSArray *components = [middle componentsSeparatedByString:@" "];
+
+ // When creating logfile if there is existing file with the same name, we append attemp number at the end.
+ // Thats why here we can have three or four components. For details see createNewLogFile method.
+ //
+ // Components:
+ // "", "2013-12-03", "17-14"
+ // or
+ // "", "2013-12-03", "17-14", "1"
+ if (components.count == 3 || components.count == 4) {
+ NSString *dateString = [NSString stringWithFormat:@"%@ %@", components[1], components[2]];
+ NSDateFormatter *dateFormatter = [self logFileDateFormatter];
+
+ NSDate *date = [dateFormatter dateFromString:dateString];
+
+ if (date) {
+ hasProperDate = YES;
+ }
+ }
+ }
+ }
+
+ return (hasProperPrefix && hasProperDate && hasProperSuffix);
+}
+
+- (NSDateFormatter *)logFileDateFormatter {
+ NSMutableDictionary *dictionary = [[NSThread currentThread]
+ threadDictionary];
+ NSString *dateFormat = @"yyyy'-'MM'-'dd' 'HH'-'mm'";
+ NSString *key = [NSString stringWithFormat:@"logFileDateFormatter.%@", dateFormat];
+ NSDateFormatter *dateFormatter = dictionary[key];
+
+ if (dateFormatter == nil) {
+ dateFormatter = [[NSDateFormatter alloc] init];
+ [dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
+ [dateFormatter setDateFormat:dateFormat];
+ [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
+ dictionary[key] = dateFormatter;
+ }
+
+ return dateFormatter;
+}
+
+- (NSArray *)unsortedLogFilePaths {
+ NSString *logsDirectory = [self logsDirectory];
+ NSArray *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:logsDirectory error:nil];
+
+ NSMutableArray *unsortedLogFilePaths = [NSMutableArray arrayWithCapacity:[fileNames count]];
+
+ for (NSString *fileName in fileNames) {
+ // Filter out any files that aren't log files. (Just for extra safety)
+
+ #if TARGET_IPHONE_SIMULATOR
+ // In case of iPhone simulator there can be 'archived' extension. isLogFile:
+ // method knows nothing about it. Thus removing it for this method.
+ //
+ // See full explanation in the header file.
+ NSString *theFileName = [fileName stringByReplacingOccurrencesOfString:@".archived"
+ withString:@""];
+
+ if ([self isLogFile:theFileName])
+ #else
+
+ if ([self isLogFile:fileName])
+ #endif
+ {
+ NSString *filePath = [logsDirectory stringByAppendingPathComponent:fileName];
+
+ [unsortedLogFilePaths addObject:filePath];
+ }
+ }
+
+ return unsortedLogFilePaths;
+}
+
+- (NSArray *)unsortedLogFileNames {
+ NSArray *unsortedLogFilePaths = [self unsortedLogFilePaths];
+
+ NSMutableArray *unsortedLogFileNames = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]];
+
+ for (NSString *filePath in unsortedLogFilePaths) {
+ [unsortedLogFileNames addObject:[filePath lastPathComponent]];
+ }
+
+ return unsortedLogFileNames;
+}
+
+- (NSArray *)unsortedLogFileInfos {
+ NSArray *unsortedLogFilePaths = [self unsortedLogFilePaths];
+
+ NSMutableArray *unsortedLogFileInfos = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]];
+
+ for (NSString *filePath in unsortedLogFilePaths) {
+ DDLogFileInfo *logFileInfo = [[DDLogFileInfo alloc] initWithFilePath:filePath];
+
+ [unsortedLogFileInfos addObject:logFileInfo];
+ }
+
+ return unsortedLogFileInfos;
+}
+
+- (NSArray *)sortedLogFilePaths {
+ NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
+
+ NSMutableArray *sortedLogFilePaths = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]];
+
+ for (DDLogFileInfo *logFileInfo in sortedLogFileInfos) {
+ [sortedLogFilePaths addObject:[logFileInfo filePath]];
+ }
+
+ return sortedLogFilePaths;
+}
+
+- (NSArray *)sortedLogFileNames {
+ NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
+
+ NSMutableArray *sortedLogFileNames = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]];
+
+ for (DDLogFileInfo *logFileInfo in sortedLogFileInfos) {
+ [sortedLogFileNames addObject:[logFileInfo fileName]];
+ }
+
+ return sortedLogFileNames;
+}
+
+- (NSArray *)sortedLogFileInfos {
+ return [[self unsortedLogFileInfos] sortedArrayUsingSelector:@selector(reverseCompareByCreationDate:)];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Creation
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSString *)newLogFileName {
+ NSString *appName = [self applicationName];
+
+ NSDateFormatter *dateFormatter = [self logFileDateFormatter];
+ NSString *formattedDate = [dateFormatter stringFromDate:[NSDate date]];
+
+ return [NSString stringWithFormat:@"%@ %@.log", appName, formattedDate];
+}
+
+- (NSString *)createNewLogFile {
+ NSString *fileName = [self newLogFileName];
+ NSString *logsDirectory = [self logsDirectory];
+
+ NSUInteger attempt = 1;
+
+ do {
+ NSString *actualFileName = fileName;
+
+ if (attempt > 1) {
+ NSString *extension = [actualFileName pathExtension];
+
+ actualFileName = [actualFileName stringByDeletingPathExtension];
+ actualFileName = [actualFileName stringByAppendingFormat:@" %lu", (unsigned long)attempt];
+
+ if (extension.length) {
+ actualFileName = [actualFileName stringByAppendingPathExtension:extension];
+ }
+ }
+
+ NSString *filePath = [logsDirectory stringByAppendingPathComponent:actualFileName];
+
+ if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
+ NSLogVerbose(@"DDLogFileManagerDefault: Creating new log file: %@", actualFileName);
+
+ NSDictionary *attributes = nil;
+
+ #if TARGET_OS_IPHONE
+ // When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
+ //
+ // But in case if app is able to launch from background we need to have an ability to open log file any time we
+ // want (even if device is locked). Thats why that attribute have to be changed to
+ // NSFileProtectionCompleteUntilFirstUserAuthentication.
+
+ NSString *key = _defaultFileProtectionLevel ? :
+ (doesAppRunInBackground() ? NSFileProtectionCompleteUntilFirstUserAuthentication : NSFileProtectionCompleteUnlessOpen);
+
+ attributes = @{
+ NSFileProtectionKey: key
+ };
+ #endif
+
+ [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:attributes];
+
+ // Since we just created a new log file, we may need to delete some old log files
+ [self deleteOldLogFiles];
+
+ return filePath;
+ } else {
+ attempt++;
+ }
+ } while (YES);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Utility
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSString *)applicationName {
+ static NSString *_appName;
+ static dispatch_once_t onceToken;
+
+ dispatch_once(&onceToken, ^{
+ _appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"];
+
+ if (!_appName) {
+ _appName = [[NSProcessInfo processInfo] processName];
+ }
+
+ if (!_appName) {
+ _appName = @"";
+ }
+ });
+
+ return _appName;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface DDLogFileFormatterDefault () {
+ NSDateFormatter *_dateFormatter;
+}
+
+@end
+
+@implementation DDLogFileFormatterDefault
+
+- (instancetype)init {
+ return [self initWithDateFormatter:nil];
+}
+
+- (instancetype)initWithDateFormatter:(NSDateFormatter *)aDateFormatter {
+ if ((self = [super init])) {
+ if (aDateFormatter) {
+ _dateFormatter = aDateFormatter;
+ } else {
+ _dateFormatter = [[NSDateFormatter alloc] init];
+ [_dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; // 10.4+ style
+ [_dateFormatter setDateFormat:@"yyyy/MM/dd HH:mm:ss:SSS"];
+ }
+ }
+
+ return self;
+}
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+ NSString *dateAndTime = [_dateFormatter stringFromDate:(logMessage->_timestamp)];
+
+ return [NSString stringWithFormat:@"%@ %@", dateAndTime, logMessage->_message];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface DDFileLogger () {
+ __strong id <DDLogFileManager> _logFileManager;
+
+ DDLogFileInfo *_currentLogFileInfo;
+ NSFileHandle *_currentLogFileHandle;
+
+ dispatch_source_t _currentLogFileVnode;
+ dispatch_source_t _rollingTimer;
+
+ unsigned long long _maximumFileSize;
+ NSTimeInterval _rollingFrequency;
+}
+
+- (void)rollLogFileNow;
+- (void)maybeRollLogFileDueToAge;
+- (void)maybeRollLogFileDueToSize;
+
+@end
+
+@implementation DDFileLogger
+
+- (instancetype)init {
+ DDLogFileManagerDefault *defaultLogFileManager = [[DDLogFileManagerDefault alloc] init];
+
+ return [self initWithLogFileManager:defaultLogFileManager];
+}
+
+- (instancetype)initWithLogFileManager:(id <DDLogFileManager>)aLogFileManager {
+ if ((self = [super init])) {
+ _maximumFileSize = kDDDefaultLogMaxFileSize;
+ _rollingFrequency = kDDDefaultLogRollingFrequency;
+ _automaticallyAppendNewlineForCustomFormatters = YES;
+
+ logFileManager = aLogFileManager;
+
+ self.logFormatter = [DDLogFileFormatterDefault new];
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ [_currentLogFileHandle synchronizeFile];
+ [_currentLogFileHandle closeFile];
+
+ if (_currentLogFileVnode) {
+ dispatch_source_cancel(_currentLogFileVnode);
+ _currentLogFileVnode = NULL;
+ }
+
+ if (_rollingTimer) {
+ dispatch_source_cancel(_rollingTimer);
+ _rollingTimer = NULL;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Properties
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@synthesize logFileManager;
+
+- (unsigned long long)maximumFileSize {
+ __block unsigned long long result;
+
+ dispatch_block_t block = ^{
+ result = _maximumFileSize;
+ };
+
+ // The design of this method is taken from the DDAbstractLogger implementation.
+ // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+ // Note: The internal implementation MUST access the maximumFileSize variable directly,
+ // This method is designed explicitly for external access.
+ //
+ // Using "self." syntax to go through this method will cause immediate deadlock.
+ // This is the intended result. Fix it by accessing the ivar directly.
+ // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+ NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
+
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+
+ dispatch_sync(globalLoggingQueue, ^{
+ dispatch_sync(self.loggerQueue, block);
+ });
+
+ return result;
+}
+
+- (void)setMaximumFileSize:(unsigned long long)newMaximumFileSize {
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ _maximumFileSize = newMaximumFileSize;
+ [self maybeRollLogFileDueToSize];
+ }
+ };
+
+ // The design of this method is taken from the DDAbstractLogger implementation.
+ // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+ // Note: The internal implementation MUST access the maximumFileSize variable directly,
+ // This method is designed explicitly for external access.
+ //
+ // Using "self." syntax to go through this method will cause immediate deadlock.
+ // This is the intended result. Fix it by accessing the ivar directly.
+ // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+ NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
+
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+}
+
+- (NSTimeInterval)rollingFrequency {
+ __block NSTimeInterval result;
+
+ dispatch_block_t block = ^{
+ result = _rollingFrequency;
+ };
+
+ // The design of this method is taken from the DDAbstractLogger implementation.
+ // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+ // Note: The internal implementation should access the rollingFrequency variable directly,
+ // This method is designed explicitly for external access.
+ //
+ // Using "self." syntax to go through this method will cause immediate deadlock.
+ // This is the intended result. Fix it by accessing the ivar directly.
+ // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+ NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
+
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+
+ dispatch_sync(globalLoggingQueue, ^{
+ dispatch_sync(self.loggerQueue, block);
+ });
+
+ return result;
+}
+
+- (void)setRollingFrequency:(NSTimeInterval)newRollingFrequency {
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ _rollingFrequency = newRollingFrequency;
+ [self maybeRollLogFileDueToAge];
+ }
+ };
+
+ // The design of this method is taken from the DDAbstractLogger implementation.
+ // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+ // Note: The internal implementation should access the rollingFrequency variable directly,
+ // This method is designed explicitly for external access.
+ //
+ // Using "self." syntax to go through this method will cause immediate deadlock.
+ // This is the intended result. Fix it by accessing the ivar directly.
+ // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+ NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
+
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark File Rolling
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)scheduleTimerToRollLogFileDueToAge {
+ if (_rollingTimer) {
+ dispatch_source_cancel(_rollingTimer);
+ _rollingTimer = NULL;
+ }
+
+ if (_currentLogFileInfo == nil || _rollingFrequency <= 0.0) {
+ return;
+ }
+
+ NSDate *logFileCreationDate = [_currentLogFileInfo creationDate];
+
+ NSTimeInterval ti = [logFileCreationDate timeIntervalSinceReferenceDate];
+ ti += _rollingFrequency;
+
+ NSDate *logFileRollingDate = [NSDate dateWithTimeIntervalSinceReferenceDate:ti];
+
+ NSLogVerbose(@"DDFileLogger: scheduleTimerToRollLogFileDueToAge");
+
+ NSLogVerbose(@"DDFileLogger: logFileCreationDate: %@", logFileCreationDate);
+ NSLogVerbose(@"DDFileLogger: logFileRollingDate : %@", logFileRollingDate);
+
+ _rollingTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue);
+
+ dispatch_source_set_event_handler(_rollingTimer, ^{ @autoreleasepool {
+ [self maybeRollLogFileDueToAge];
+ } });
+
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_source_t theRollingTimer = _rollingTimer;
+ dispatch_source_set_cancel_handler(_rollingTimer, ^{
+ dispatch_release(theRollingTimer);
+ });
+ #endif
+
+ uint64_t delay = (uint64_t)([logFileRollingDate timeIntervalSinceNow] * NSEC_PER_SEC);
+ dispatch_time_t fireTime = dispatch_time(DISPATCH_TIME_NOW, delay);
+
+ dispatch_source_set_timer(_rollingTimer, fireTime, DISPATCH_TIME_FOREVER, 1.0);
+ dispatch_resume(_rollingTimer);
+}
+
+- (void)rollLogFile {
+ [self rollLogFileWithCompletionBlock:nil];
+}
+
+- (void)rollLogFileWithCompletionBlock:(void (^)())completionBlock {
+ // This method is public.
+ // We need to execute the rolling on our logging thread/queue.
+
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ [self rollLogFileNow];
+
+ if (completionBlock) {
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ completionBlock();
+ });
+ }
+ }
+ };
+
+ // The design of this method is taken from the DDAbstractLogger implementation.
+ // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+ if ([self isOnInternalLoggerQueue]) {
+ block();
+ } else {
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+ }
+}
+
+- (void)rollLogFileNow {
+ NSLogVerbose(@"DDFileLogger: rollLogFileNow");
+
+ if (_currentLogFileHandle == nil) {
+ return;
+ }
+
+ [_currentLogFileHandle synchronizeFile];
+ [_currentLogFileHandle closeFile];
+ _currentLogFileHandle = nil;
+
+ _currentLogFileInfo.isArchived = YES;
+
+ if ([logFileManager respondsToSelector:@selector(didRollAndArchiveLogFile:)]) {
+ [logFileManager didRollAndArchiveLogFile:(_currentLogFileInfo.filePath)];
+ }
+
+ _currentLogFileInfo = nil;
+
+ if (_currentLogFileVnode) {
+ dispatch_source_cancel(_currentLogFileVnode);
+ _currentLogFileVnode = NULL;
+ }
+
+ if (_rollingTimer) {
+ dispatch_source_cancel(_rollingTimer);
+ _rollingTimer = NULL;
+ }
+}
+
+- (void)maybeRollLogFileDueToAge {
+ if (_rollingFrequency > 0.0 && _currentLogFileInfo.age >= _rollingFrequency) {
+ NSLogVerbose(@"DDFileLogger: Rolling log file due to age...");
+
+ [self rollLogFileNow];
+ } else {
+ [self scheduleTimerToRollLogFileDueToAge];
+ }
+}
+
+- (void)maybeRollLogFileDueToSize {
+ // This method is called from logMessage.
+ // Keep it FAST.
+
+ // Note: Use direct access to maximumFileSize variable.
+ // We specifically wrote our own getter/setter method to allow us to do this (for performance reasons).
+
+ if (_maximumFileSize > 0) {
+ unsigned long long fileSize = [_currentLogFileHandle offsetInFile];
+
+ if (fileSize >= _maximumFileSize) {
+ NSLogVerbose(@"DDFileLogger: Rolling log file due to size (%qu)...", fileSize);
+
+ [self rollLogFileNow];
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark File Logging
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns the log file that should be used.
+ * If there is an existing log file that is suitable,
+ * within the constraints of maximumFileSize and rollingFrequency, then it is returned.
+ *
+ * Otherwise a new file is created and returned.
+ **/
+- (DDLogFileInfo *)currentLogFileInfo {
+ if (_currentLogFileInfo == nil) {
+ NSArray *sortedLogFileInfos = [logFileManager sortedLogFileInfos];
+
+ if ([sortedLogFileInfos count] > 0) {
+ DDLogFileInfo *mostRecentLogFileInfo = sortedLogFileInfos[0];
+
+ BOOL shouldArchiveMostRecent = NO;
+
+ if (mostRecentLogFileInfo.isArchived) {
+ shouldArchiveMostRecent = NO;
+ } else if (_maximumFileSize > 0 && mostRecentLogFileInfo.fileSize >= _maximumFileSize) {
+ shouldArchiveMostRecent = YES;
+ } else if (_rollingFrequency > 0.0 && mostRecentLogFileInfo.age >= _rollingFrequency) {
+ shouldArchiveMostRecent = YES;
+ }
+
+ #if TARGET_OS_IPHONE
+ // When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
+ //
+ // But in case if app is able to launch from background we need to have an ability to open log file any time we
+ // want (even if device is locked). Thats why that attribute have to be changed to
+ // NSFileProtectionCompleteUntilFirstUserAuthentication.
+ //
+ // If previous log was created when app wasn't running in background, but now it is - we archive it and create
+ // a new one.
+ //
+ // If user has owerwritten to NSFileProtectionNone there is no neeed to create a new one.
+
+ if (!_doNotReuseLogFiles && doesAppRunInBackground()) {
+ NSString *key = mostRecentLogFileInfo.fileAttributes[NSFileProtectionKey];
+
+ if ([key length] > 0 && !([key isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication] || [key isEqualToString:NSFileProtectionNone])) {
+ shouldArchiveMostRecent = YES;
+ }
+ }
+
+ #endif
+
+ if (!_doNotReuseLogFiles && !mostRecentLogFileInfo.isArchived && !shouldArchiveMostRecent) {
+ NSLogVerbose(@"DDFileLogger: Resuming logging with file %@", mostRecentLogFileInfo.fileName);
+
+ _currentLogFileInfo = mostRecentLogFileInfo;
+ } else {
+ if (shouldArchiveMostRecent) {
+ mostRecentLogFileInfo.isArchived = YES;
+
+ if ([logFileManager respondsToSelector:@selector(didArchiveLogFile:)]) {
+ [logFileManager didArchiveLogFile:(mostRecentLogFileInfo.filePath)];
+ }
+ }
+ }
+ }
+
+ if (_currentLogFileInfo == nil) {
+ NSString *currentLogFilePath = [logFileManager createNewLogFile];
+
+ _currentLogFileInfo = [[DDLogFileInfo alloc] initWithFilePath:currentLogFilePath];
+ }
+ }
+
+ return _currentLogFileInfo;
+}
+
+- (NSFileHandle *)currentLogFileHandle {
+ if (_currentLogFileHandle == nil) {
+ NSString *logFilePath = [[self currentLogFileInfo] filePath];
+
+ _currentLogFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
+ [_currentLogFileHandle seekToEndOfFile];
+
+ if (_currentLogFileHandle) {
+ [self scheduleTimerToRollLogFileDueToAge];
+
+ // Here we are monitoring the log file. In case if it would be deleted ormoved
+ // somewhere we want to roll it and use a new one.
+ _currentLogFileVnode = dispatch_source_create(
+ DISPATCH_SOURCE_TYPE_VNODE,
+ [_currentLogFileHandle fileDescriptor],
+ DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME,
+ self.loggerQueue
+ );
+
+ dispatch_source_set_event_handler(_currentLogFileVnode, ^{ @autoreleasepool {
+ NSLogInfo(@"DDFileLogger: Current logfile was moved. Rolling it and creating a new one");
+ [self rollLogFileNow];
+ } });
+
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_source_t vnode = _currentLogFileVnode;
+ dispatch_source_set_cancel_handler(_currentLogFileVnode, ^{
+ dispatch_release(vnode);
+ });
+ #endif
+
+ dispatch_resume(_currentLogFileVnode);
+ }
+ }
+
+ return _currentLogFileHandle;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark DDLogger Protocol
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static int exception_count = 0;
+- (void)logMessage:(DDLogMessage *)logMessage {
+ NSString *message = logMessage->_message;
+ BOOL isFormatted = NO;
+
+ if (_logFormatter) {
+ message = [_logFormatter formatLogMessage:logMessage];
+ isFormatted = message != logMessage->_message;
+ }
+
+ if (message) {
+ if ((!isFormatted || _automaticallyAppendNewlineForCustomFormatters) &&
+ (![message hasSuffix:@"\n"])) {
+ message = [message stringByAppendingString:@"\n"];
+ }
+
+ NSData *logData = [message dataUsingEncoding:NSUTF8StringEncoding];
+
+ @try {
+ [[self currentLogFileHandle] writeData:logData];
+
+ [self maybeRollLogFileDueToSize];
+ } @catch (NSException *exception) {
+ exception_count++;
+
+ if (exception_count <= 10) {
+ NSLogError(@"DDFileLogger.logMessage: %@", exception);
+
+ if (exception_count == 10) {
+ NSLogError(@"DDFileLogger.logMessage: Too many exceptions -- will not log any more of them.");
+ }
+ }
+ }
+ }
+}
+
+- (void)willRemoveLogger {
+ // If you override me be sure to invoke [super willRemoveLogger];
+
+ [self rollLogFileNow];
+}
+
+- (NSString *)loggerName {
+ return @"cocoa.lumberjack.fileLogger";
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if TARGET_IPHONE_SIMULATOR
+ static NSString * const kDDXAttrArchivedName = @"archived";
+#else
+ static NSString * const kDDXAttrArchivedName = @"lumberjack.log.archived";
+#endif
+
+@interface DDLogFileInfo () {
+ __strong NSString *_filePath;
+ __strong NSString *_fileName;
+
+ __strong NSDictionary *_fileAttributes;
+
+ __strong NSDate *_creationDate;
+ __strong NSDate *_modificationDate;
+
+ unsigned long long _fileSize;
+}
+
+@end
+
+
+@implementation DDLogFileInfo
+
+@synthesize filePath;
+
+@dynamic fileName;
+@dynamic fileAttributes;
+@dynamic creationDate;
+@dynamic modificationDate;
+@dynamic fileSize;
+@dynamic age;
+
+@dynamic isArchived;
+
+
+#pragma mark Lifecycle
+
++ (instancetype)logFileWithPath:(NSString *)aFilePath {
+ return [[self alloc] initWithFilePath:aFilePath];
+}
+
+- (instancetype)initWithFilePath:(NSString *)aFilePath {
+ if ((self = [super init])) {
+ filePath = [aFilePath copy];
+ }
+
+ return self;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Standard Info
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSDictionary *)fileAttributes {
+ if (_fileAttributes == nil) {
+ _fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
+ }
+
+ return _fileAttributes;
+}
+
+- (NSString *)fileName {
+ if (_fileName == nil) {
+ _fileName = [filePath lastPathComponent];
+ }
+
+ return _fileName;
+}
+
+- (NSDate *)modificationDate {
+ if (_modificationDate == nil) {
+ _modificationDate = self.fileAttributes[NSFileModificationDate];
+ }
+
+ return _modificationDate;
+}
+
+- (NSDate *)creationDate {
+ if (_creationDate == nil) {
+ _creationDate = self.fileAttributes[NSFileCreationDate];
+ }
+
+ return _creationDate;
+}
+
+- (unsigned long long)fileSize {
+ if (_fileSize == 0) {
+ _fileSize = [self.fileAttributes[NSFileSize] unsignedLongLongValue];
+ }
+
+ return _fileSize;
+}
+
+- (NSTimeInterval)age {
+ return [[self creationDate] timeIntervalSinceNow] * -1.0;
+}
+
+- (NSString *)description {
+ return [@{ @"filePath": self.filePath ? : @"",
+ @"fileName": self.fileName ? : @"",
+ @"fileAttributes": self.fileAttributes ? : @"",
+ @"creationDate": self.creationDate ? : @"",
+ @"modificationDate": self.modificationDate ? : @"",
+ @"fileSize": @(self.fileSize),
+ @"age": @(self.age),
+ @"isArchived": @(self.isArchived) } description];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Archiving
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)isArchived {
+#if TARGET_IPHONE_SIMULATOR
+
+ // Extended attributes don't work properly on the simulator.
+ // So we have to use a less attractive alternative.
+ // See full explanation in the header file.
+
+ return [self hasExtensionAttributeWithName:kDDXAttrArchivedName];
+
+#else
+
+ return [self hasExtendedAttributeWithName:kDDXAttrArchivedName];
+
+#endif
+}
+
+- (void)setIsArchived:(BOOL)flag {
+#if TARGET_IPHONE_SIMULATOR
+
+ // Extended attributes don't work properly on the simulator.
+ // So we have to use a less attractive alternative.
+ // See full explanation in the header file.
+
+ if (flag) {
+ [self addExtensionAttributeWithName:kDDXAttrArchivedName];
+ } else {
+ [self removeExtensionAttributeWithName:kDDXAttrArchivedName];
+ }
+
+#else
+
+ if (flag) {
+ [self addExtendedAttributeWithName:kDDXAttrArchivedName];
+ } else {
+ [self removeExtendedAttributeWithName:kDDXAttrArchivedName];
+ }
+
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Changes
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)reset {
+ _fileName = nil;
+ _fileAttributes = nil;
+ _creationDate = nil;
+ _modificationDate = nil;
+}
+
+- (void)renameFile:(NSString *)newFileName {
+ // This method is only used on the iPhone simulator, where normal extended attributes are broken.
+ // See full explanation in the header file.
+
+ if (![newFileName isEqualToString:[self fileName]]) {
+ NSString *fileDir = [filePath stringByDeletingLastPathComponent];
+
+ NSString *newFilePath = [fileDir stringByAppendingPathComponent:newFileName];
+
+ NSLogVerbose(@"DDLogFileInfo: Renaming file: '%@' -> '%@'", self.fileName, newFileName);
+
+ NSError *error = nil;
+
+ if ([[NSFileManager defaultManager] fileExistsAtPath:newFilePath] &&
+ ![[NSFileManager defaultManager] removeItemAtPath:newFilePath error:&error]) {
+ NSLogError(@"DDLogFileInfo: Error deleting archive (%@): %@", self.fileName, error);
+ }
+
+ if (![[NSFileManager defaultManager] moveItemAtPath:filePath toPath:newFilePath error:&error]) {
+ NSLogError(@"DDLogFileInfo: Error renaming file (%@): %@", self.fileName, error);
+ }
+
+ filePath = newFilePath;
+ [self reset];
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Attribute Management
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if TARGET_IPHONE_SIMULATOR
+
+// Extended attributes don't work properly on the simulator.
+// So we have to use a less attractive alternative.
+// See full explanation in the header file.
+
+- (BOOL)hasExtensionAttributeWithName:(NSString *)attrName {
+ // This method is only used on the iPhone simulator, where normal extended attributes are broken.
+ // See full explanation in the header file.
+
+ // Split the file name into components. File name may have various format, but generally
+ // structure is same:
+ //
+ // <name part>.<extension part> and <name part>.archived.<extension part>
+ // or
+ // <name part> and <name part>.archived
+ //
+ // So we want to search for the attrName in the components (ignoring the first array index).
+
+ NSArray *components = [[self fileName] componentsSeparatedByString:@"."];
+
+ // Watch out for file names without an extension
+
+ for (NSUInteger i = 1; i < components.count; i++) {
+ NSString *attr = components[i];
+
+ if ([attrName isEqualToString:attr]) {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
+- (void)addExtensionAttributeWithName:(NSString *)attrName {
+ // This method is only used on the iPhone simulator, where normal extended attributes are broken.
+ // See full explanation in the header file.
+
+ if ([attrName length] == 0) {
+ return;
+ }
+
+ // Example:
+ // attrName = "archived"
+ //
+ // "mylog.txt" -> "mylog.archived.txt"
+ // "mylog" -> "mylog.archived"
+
+ NSArray *components = [[self fileName] componentsSeparatedByString:@"."];
+
+ NSUInteger count = [components count];
+
+ NSUInteger estimatedNewLength = [[self fileName] length] + [attrName length] + 1;
+ NSMutableString *newFileName = [NSMutableString stringWithCapacity:estimatedNewLength];
+
+ if (count > 0) {
+ [newFileName appendString:components.firstObject];
+ }
+
+ NSString *lastExt = @"";
+
+ NSUInteger i;
+
+ for (i = 1; i < count; i++) {
+ NSString *attr = components[i];
+
+ if ([attr length] == 0) {
+ continue;
+ }
+
+ if ([attrName isEqualToString:attr]) {
+ // Extension attribute already exists in file name
+ return;
+ }
+
+ if ([lastExt length] > 0) {
+ [newFileName appendFormat:@".%@", lastExt];
+ }
+
+ lastExt = attr;
+ }
+
+ [newFileName appendFormat:@".%@", attrName];
+
+ if ([lastExt length] > 0) {
+ [newFileName appendFormat:@".%@", lastExt];
+ }
+
+ [self renameFile:newFileName];
+}
+
+- (void)removeExtensionAttributeWithName:(NSString *)attrName {
+ // This method is only used on the iPhone simulator, where normal extended attributes are broken.
+ // See full explanation in the header file.
+
+ if ([attrName length] == 0) {
+ return;
+ }
+
+ // Example:
+ // attrName = "archived"
+ //
+ // "mylog.archived.txt" -> "mylog.txt"
+ // "mylog.archived" -> "mylog"
+
+ NSArray *components = [[self fileName] componentsSeparatedByString:@"."];
+
+ NSUInteger count = [components count];
+
+ NSUInteger estimatedNewLength = [[self fileName] length];
+ NSMutableString *newFileName = [NSMutableString stringWithCapacity:estimatedNewLength];
+
+ if (count > 0) {
+ [newFileName appendString:components.firstObject];
+ }
+
+ BOOL found = NO;
+
+ NSUInteger i;
+
+ for (i = 1; i < count; i++) {
+ NSString *attr = components[i];
+
+ if ([attrName isEqualToString:attr]) {
+ found = YES;
+ } else {
+ [newFileName appendFormat:@".%@", attr];
+ }
+ }
+
+ if (found) {
+ [self renameFile:newFileName];
+ }
+}
+
+#else /* if TARGET_IPHONE_SIMULATOR */
+
+- (BOOL)hasExtendedAttributeWithName:(NSString *)attrName {
+ const char *path = [filePath UTF8String];
+ const char *name = [attrName UTF8String];
+
+ ssize_t result = getxattr(path, name, NULL, 0, 0, 0);
+
+ return (result >= 0);
+}
+
+- (void)addExtendedAttributeWithName:(NSString *)attrName {
+ const char *path = [filePath UTF8String];
+ const char *name = [attrName UTF8String];
+
+ int result = setxattr(path, name, NULL, 0, 0, 0);
+
+ if (result < 0) {
+ NSLogError(@"DDLogFileInfo: setxattr(%@, %@): error = %s",
+ attrName,
+ filePath,
+ strerror(errno));
+ }
+}
+
+- (void)removeExtendedAttributeWithName:(NSString *)attrName {
+ const char *path = [filePath UTF8String];
+ const char *name = [attrName UTF8String];
+
+ int result = removexattr(path, name, 0);
+
+ if (result < 0 && errno != ENOATTR) {
+ NSLogError(@"DDLogFileInfo: removexattr(%@, %@): error = %s",
+ attrName,
+ self.fileName,
+ strerror(errno));
+ }
+}
+
+#endif /* if TARGET_IPHONE_SIMULATOR */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Comparisons
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)isEqual:(id)object {
+ if ([object isKindOfClass:[self class]]) {
+ DDLogFileInfo *another = (DDLogFileInfo *)object;
+
+ return [filePath isEqualToString:[another filePath]];
+ }
+
+ return NO;
+}
+
+- (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another {
+ NSDate *us = [self creationDate];
+ NSDate *them = [another creationDate];
+
+ NSComparisonResult result = [us compare:them];
+
+ if (result == NSOrderedAscending) {
+ return NSOrderedDescending;
+ }
+
+ if (result == NSOrderedDescending) {
+ return NSOrderedAscending;
+ }
+
+ return NSOrderedSame;
+}
+
+- (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another {
+ NSDate *us = [self modificationDate];
+ NSDate *them = [another modificationDate];
+
+ NSComparisonResult result = [us compare:them];
+
+ if (result == NSOrderedAscending) {
+ return NSOrderedDescending;
+ }
+
+ if (result == NSOrderedDescending) {
+ return NSOrderedAscending;
+ }
+
+ return NSOrderedSame;
+}
+
+@end
+
+#if TARGET_OS_IPHONE
+/**
+ * When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
+ *
+ * But in case if app is able to launch from background we need to have an ability to open log file any time we
+ * want (even if device is locked). Thats why that attribute have to be changed to
+ * NSFileProtectionCompleteUntilFirstUserAuthentication.
+ */
+BOOL doesAppRunInBackground() {
+ BOOL answer = NO;
+
+ NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
+
+ for (NSString *mode in backgroundModes) {
+ if (mode.length > 0) {
+ answer = YES;
+ break;
+ }
+ }
+
+ return answer;
+}
+
+#endif
diff --git a/cocoalumberjack/Classes/DDLegacyMacros.h b/cocoalumberjack/Classes/DDLegacyMacros.h
new file mode 100644
index 0000000..e0671b9
--- /dev/null
+++ b/cocoalumberjack/Classes/DDLegacyMacros.h
@@ -0,0 +1,75 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+/**
+ * Legacy macros used for 1.9.x backwards compatibility.
+ *
+ * Imported by default when importing a DDLog.h directly and DD_LEGACY_MACROS is not defined and set to 0.
+ **/
+#if DD_LEGACY_MACROS
+
+#warning CocoaLumberjack 1.9.x legacy macros enabled. \
+Disable legacy macros by importing CocoaLumberjack.h or DDLogMacros.h instead of DDLog.h or add `#define DD_LEGACY_MACROS 0` before importing DDLog.h.
+
+#ifndef LOG_LEVEL_DEF
+ #define LOG_LEVEL_DEF ddLogLevel
+#endif
+
+#define LOG_FLAG_ERROR DDLogFlagError
+#define LOG_FLAG_WARN DDLogFlagWarning
+#define LOG_FLAG_INFO DDLogFlagInfo
+#define LOG_FLAG_DEBUG DDLogFlagDebug
+#define LOG_FLAG_VERBOSE DDLogFlagVerbose
+
+#define LOG_LEVEL_OFF DDLogLevelOff
+#define LOG_LEVEL_ERROR DDLogLevelError
+#define LOG_LEVEL_WARN DDLogLevelWarning
+#define LOG_LEVEL_INFO DDLogLevelInfo
+#define LOG_LEVEL_DEBUG DDLogLevelDebug
+#define LOG_LEVEL_VERBOSE DDLogLevelVerbose
+#define LOG_LEVEL_ALL DDLogLevelAll
+
+#define LOG_ASYNC_ENABLED YES
+
+#define LOG_ASYNC_ERROR ( NO && LOG_ASYNC_ENABLED)
+#define LOG_ASYNC_WARN (YES && LOG_ASYNC_ENABLED)
+#define LOG_ASYNC_INFO (YES && LOG_ASYNC_ENABLED)
+#define LOG_ASYNC_DEBUG (YES && LOG_ASYNC_ENABLED)
+#define LOG_ASYNC_VERBOSE (YES && LOG_ASYNC_ENABLED)
+
+#define LOG_MACRO(isAsynchronous, lvl, flg, ctx, atag, fnct, frmt, ...) \
+ [DDLog log : isAsynchronous \
+ level : lvl \
+ flag : flg \
+ context : ctx \
+ file : __FILE__ \
+ function : fnct \
+ line : __LINE__ \
+ tag : atag \
+ format : (frmt), ## __VA_ARGS__]
+
+#define LOG_MAYBE(async, lvl, flg, ctx, fnct, frmt, ...) \
+ do { if(lvl & flg) LOG_MACRO(async, lvl, flg, ctx, nil, fnct, frmt, ##__VA_ARGS__); } while(0)
+
+#define LOG_OBJC_MAYBE(async, lvl, flg, ctx, frmt, ...) \
+ LOG_MAYBE(async, lvl, flg, ctx, __PRETTY_FUNCTION__, frmt, ## __VA_ARGS__)
+
+#define DDLogError(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_ERROR, LOG_LEVEL_DEF, LOG_FLAG_ERROR, 0, frmt, ##__VA_ARGS__)
+#define DDLogWarn(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_WARN, LOG_LEVEL_DEF, LOG_FLAG_WARN, 0, frmt, ##__VA_ARGS__)
+#define DDLogInfo(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_INFO, LOG_LEVEL_DEF, LOG_FLAG_INFO, 0, frmt, ##__VA_ARGS__)
+#define DDLogDebug(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_DEBUG, LOG_LEVEL_DEF, LOG_FLAG_DEBUG, 0, frmt, ##__VA_ARGS__)
+#define DDLogVerbose(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_VERBOSE, LOG_LEVEL_DEF, LOG_FLAG_VERBOSE, 0, frmt, ##__VA_ARGS__)
+
+#endif
diff --git a/cocoalumberjack/Classes/DDLog+LOGV.h b/cocoalumberjack/Classes/DDLog+LOGV.h
new file mode 100644
index 0000000..cf4bfc3
--- /dev/null
+++ b/cocoalumberjack/Classes/DDLog+LOGV.h
@@ -0,0 +1,83 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+ #define DD_LEGACY_MACROS 0
+#endif
+
+#import "DDLog.h"
+
+/**
+ * The constant/variable/method responsible for controlling the current log level.
+ **/
+#ifndef LOG_LEVEL_DEF
+ #define LOG_LEVEL_DEF ddLogLevel
+#endif
+
+/**
+ * Whether async should be used by log messages, excluding error messages that are always sent sync.
+ **/
+#ifndef LOG_ASYNC_ENABLED
+ #define LOG_ASYNC_ENABLED YES
+#endif
+
+/**
+ * This is the single macro that all other macros below compile into.
+ * This big multiline macro makes all the other macros easier to read.
+ **/
+#define LOGV_MACRO(isAsynchronous, lvl, flg, ctx, atag, fnct, frmt, avalist) \
+ [DDLog log : isAsynchronous \
+ level : lvl \
+ flag : flg \
+ context : ctx \
+ file : __FILE__ \
+ function : fnct \
+ line : __LINE__ \
+ tag : atag \
+ format : frmt \
+ args : avalist]
+
+/**
+ * Define version of the macro that only execute if the log level is above the threshold.
+ * The compiled versions essentially look like this:
+ *
+ * if (logFlagForThisLogMsg & ddLogLevel) { execute log message }
+ *
+ * When LOG_LEVEL_DEF is defined as ddLogLevel.
+ *
+ * As shown further below, Lumberjack actually uses a bitmask as opposed to primitive log levels.
+ * This allows for a great amount of flexibility and some pretty advanced fine grained logging techniques.
+ *
+ * Note that when compiler optimizations are enabled (as they are for your release builds),
+ * the log messages above your logging threshold will automatically be compiled out.
+ *
+ * (If the compiler sees LOG_LEVEL_DEF/ddLogLevel declared as a constant, the compiler simply checks to see
+ * if the 'if' statement would execute, and if not it strips it from the binary.)
+ *
+ * We also define shorthand versions for asynchronous and synchronous logging.
+ **/
+#define LOGV_MAYBE(async, lvl, flg, ctx, tag, fnct, frmt, avalist) \
+ do { if(lvl & flg) LOGV_MACRO(async, lvl, flg, ctx, tag, fnct, frmt, avalist); } while(0)
+
+/**
+ * Ready to use log macros with no context or tag.
+ **/
+#define DDLogVError(frmt, avalist) LOGV_MAYBE(NO, LOG_LEVEL_DEF, DDLogFlagError, 0, nil, __PRETTY_FUNCTION__, frmt, avalist)
+#define DDLogVWarn(frmt, avalist) LOGV_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagWarning, 0, nil, __PRETTY_FUNCTION__, frmt, avalist)
+#define DDLogVInfo(frmt, avalist) LOGV_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagInfo, 0, nil, __PRETTY_FUNCTION__, frmt, avalist)
+#define DDLogVDebug(frmt, avalist) LOGV_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagDebug, 0, nil, __PRETTY_FUNCTION__, frmt, avalist)
+#define DDLogVVerbose(frmt, avalist) LOGV_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagVerbose, 0, nil, __PRETTY_FUNCTION__, frmt, avalist)
+
diff --git a/cocoalumberjack/Classes/DDLog.h b/cocoalumberjack/Classes/DDLog.h
new file mode 100644
index 0000000..b7a817e
--- /dev/null
+++ b/cocoalumberjack/Classes/DDLog.h
@@ -0,0 +1,743 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import <Foundation/Foundation.h>
+
+// Enable 1.9.x legacy macros if imported directly
+#ifndef DD_LEGACY_MACROS
+ #define DD_LEGACY_MACROS 1
+#endif
+// DD_LEGACY_MACROS is checked in the file itself
+#import "DDLegacyMacros.h"
+
+#if OS_OBJECT_USE_OBJC
+ #define DISPATCH_QUEUE_REFERENCE_TYPE strong
+#else
+ #define DISPATCH_QUEUE_REFERENCE_TYPE assign
+#endif
+
+@class DDLogMessage;
+@protocol DDLogger;
+@protocol DDLogFormatter;
+
+/**
+ * Define the standard options.
+ *
+ * We default to only 4 levels because it makes it easier for beginners
+ * to make the transition to a logging framework.
+ *
+ * More advanced users may choose to completely customize the levels (and level names) to suite their needs.
+ * For more information on this see the "Custom Log Levels" page:
+ * Documentation/CustomLogLevels.md
+ *
+ * Advanced users may also notice that we're using a bitmask.
+ * This is to allow for custom fine grained logging:
+ * Documentation/FineGrainedLogging.md
+ *
+ * -- Flags --
+ *
+ * Typically you will use the LOG_LEVELS (see below), but the flags may be used directly in certain situations.
+ * For example, say you have a lot of warning log messages, and you wanted to disable them.
+ * However, you still needed to see your error and info log messages.
+ * You could accomplish that with the following:
+ *
+ * static const DDLogLevel ddLogLevel = DDLogFlagError | DDLogFlagInfo;
+ *
+ * When LOG_LEVEL_DEF is defined as ddLogLevel.
+ *
+ * Flags may also be consulted when writing custom log formatters,
+ * as the DDLogMessage class captures the individual flag that caused the log message to fire.
+ *
+ * -- Levels --
+ *
+ * Log levels are simply the proper bitmask of the flags.
+ *
+ * -- Booleans --
+ *
+ * The booleans may be used when your logging code involves more than one line.
+ * For example:
+ *
+ * if (LOG_VERBOSE) {
+ * for (id sprocket in sprockets)
+ * DDLogVerbose(@"sprocket: %@", [sprocket description])
+ * }
+ *
+ * -- Async --
+ *
+ * Defines the default asynchronous options.
+ * The default philosophy for asynchronous logging is very simple:
+ *
+ * Log messages with errors should be executed synchronously.
+ * After all, an error just occurred. The application could be unstable.
+ *
+ * All other log messages, such as debug output, are executed asynchronously.
+ * After all, if it wasn't an error, then it was just informational output,
+ * or something the application was easily able to recover from.
+ *
+ * -- Changes --
+ *
+ * You are strongly discouraged from modifying this file.
+ * If you do, you make it more difficult on yourself to merge future bug fixes and improvements from the project.
+ * Instead, create your own MyLogging.h or ApplicationNameLogging.h or CompanyLogging.h
+ *
+ * For an example of customizing your logging experience, see the "Custom Log Levels" page:
+ * Documentation/CustomLogLevels.md
+ **/
+
+/**
+ * Flags accompany each log. They are used together with levels to filter out logs.
+ */
+typedef NS_OPTIONS(NSUInteger, DDLogFlag){
+ /**
+ * 0...00001 DDLogFlagError
+ */
+ DDLogFlagError = (1 << 0),
+
+ /**
+ * 0...00010 DDLogFlagWarning
+ */
+ DDLogFlagWarning = (1 << 1),
+
+ /**
+ * 0...00100 DDLogFlagInfo
+ */
+ DDLogFlagInfo = (1 << 2),
+
+ /**
+ * 0...01000 DDLogFlagDebug
+ */
+ DDLogFlagDebug = (1 << 3),
+
+ /**
+ * 0...10000 DDLogFlagVerbose
+ */
+ DDLogFlagVerbose = (1 << 4)
+};
+
+/**
+ * Log levels are used to filter out logs. Used together with flags.
+ */
+typedef NS_ENUM(NSUInteger, DDLogLevel){
+ /**
+ * No logs
+ */
+ DDLogLevelOff = 0,
+
+ /**
+ * Error logs only
+ */
+ DDLogLevelError = (DDLogFlagError),
+
+ /**
+ * Error and warning logs
+ */
+ DDLogLevelWarning = (DDLogLevelError | DDLogFlagWarning),
+
+ /**
+ * Error, warning and info logs
+ */
+ DDLogLevelInfo = (DDLogLevelWarning | DDLogFlagInfo),
+
+ /**
+ * Error, warning, info and debug logs
+ */
+ DDLogLevelDebug = (DDLogLevelInfo | DDLogFlagDebug),
+
+ /**
+ * Error, warning, info, debug and verbose logs
+ */
+ DDLogLevelVerbose = (DDLogLevelDebug | DDLogFlagVerbose),
+
+ /**
+ * All logs (1...11111)
+ */
+ DDLogLevelAll = NSUIntegerMax
+};
+
+/**
+ * Extracts just the file name, no path or extension
+ *
+ * @param filePath input file path
+ * @param copy YES if we want the result to be copied
+ *
+ * @return the file name
+ */
+NSString * DDExtractFileNameWithoutExtension(const char *filePath, BOOL copy);
+
+/**
+ * The THIS_FILE macro gives you an NSString of the file name.
+ * For simplicity and clarity, the file name does not include the full path or file extension.
+ *
+ * For example: DDLogWarn(@"%@: Unable to find thingy", THIS_FILE) -> @"MyViewController: Unable to find thingy"
+ **/
+#define THIS_FILE (DDExtractFileNameWithoutExtension(__FILE__, NO))
+
+/**
+ * The THIS_METHOD macro gives you the name of the current objective-c method.
+ *
+ * For example: DDLogWarn(@"%@ - Requires non-nil strings", THIS_METHOD) -> @"setMake:model: requires non-nil strings"
+ *
+ * Note: This does NOT work in straight C functions (non objective-c).
+ * Instead you should use the predefined __FUNCTION__ macro.
+ **/
+#define THIS_METHOD NSStringFromSelector(_cmd)
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The main class, exposes all logging mechanisms, loggers, ...
+ * For most of the users, this class is hidden behind the logging functions like `DDLogInfo`
+ */
+@interface DDLog : NSObject
+
+/**
+ * Provides access to the underlying logging queue.
+ * This may be helpful to Logger classes for things like thread synchronization.
+ **/
++ (dispatch_queue_t)loggingQueue;
+
+/**
+ * Logging Primitive.
+ *
+ * This method is used by the macros or logging functions.
+ * It is suggested you stick with the macros as they're easier to use.
+ *
+ * @param asynchronous YES if the logging is done async, NO if you want to force sync
+ * @param level the log level
+ * @param flag the log flag
+ * @param context the context (if any is defined)
+ * @param file the current file
+ * @param function the current function
+ * @param line the current code line
+ * @param tag potential tag
+ * @param format the log format
+ */
++ (void)log:(BOOL)asynchronous
+ level:(DDLogLevel)level
+ flag:(DDLogFlag)flag
+ context:(NSInteger)context
+ file:(const char *)file
+ function:(const char *)function
+ line:(NSUInteger)line
+ tag:(id)tag
+ format:(NSString *)format, ... NS_FORMAT_FUNCTION(9,10);
+
+/**
+ * Logging Primitive.
+ *
+ * This method can be used if you have a prepared va_list.
+ * Similar to `log:level:flag:context:file:function:line:tag:format:...`
+ *
+ * @param asynchronous YES if the logging is done async, NO if you want to force sync
+ * @param level the log level
+ * @param flag the log flag
+ * @param context the context (if any is defined)
+ * @param file the current file
+ * @param function the current function
+ * @param line the current code line
+ * @param tag potential tag
+ * @param format the log format
+ * @param argList the arguments list as a va_list
+ */
++ (void)log:(BOOL)asynchronous
+ level:(DDLogLevel)level
+ flag:(DDLogFlag)flag
+ context:(NSInteger)context
+ file:(const char *)file
+ function:(const char *)function
+ line:(NSUInteger)line
+ tag:(id)tag
+ format:(NSString *)format
+ args:(va_list)argList;
+
+/**
+ * Logging Primitive.
+ *
+ * @param asynchronous YES if the logging is done async, NO if you want to force sync
+ * @param message the message
+ * @param level the log level
+ * @param flag the log flag
+ * @param context the context (if any is defined)
+ * @param file the current file
+ * @param function the current function
+ * @param line the current code line
+ * @param tag potential tag
+ */
++ (void)log:(BOOL)asynchronous
+ message:(NSString *)message
+ level:(DDLogLevel)level
+ flag:(DDLogFlag)flag
+ context:(NSInteger)context
+ file:(const char *)file
+ function:(const char *)function
+ line:(NSUInteger)line
+ tag:(id)tag;
+
+/**
+ * Logging Primitive.
+ *
+ * This method can be used if you manualy prepared DDLogMessage.
+ *
+ * @param asynchronous YES if the logging is done async, NO if you want to force sync
+ * @param logMessage the log message stored in a `DDLogMessage` model object
+ */
++ (void)log:(BOOL)asynchronous
+ message:(DDLogMessage *)logMessage;
+
+/**
+ * Since logging can be asynchronous, there may be times when you want to flush the logs.
+ * The framework invokes this automatically when the application quits.
+ **/
++ (void)flushLog;
+
+/**
+ * Loggers
+ *
+ * In order for your log statements to go somewhere, you should create and add a logger.
+ *
+ * You can add multiple loggers in order to direct your log statements to multiple places.
+ * And each logger can be configured separately.
+ * So you could have, for example, verbose logging to the console, but a concise log file with only warnings & errors.
+ **/
+
+/**
+ * Adds the logger to the system.
+ *
+ * This is equivalent to invoking `[DDLog addLogger:logger withLogLevel:DDLogLevelAll]`.
+ **/
++ (void)addLogger:(id <DDLogger>)logger;
+
+/**
+ * Adds the logger to the system.
+ *
+ * The level that you provide here is a preemptive filter (for performance).
+ * That is, the level specified here will be used to filter out logMessages so that
+ * the logger is never even invoked for the messages.
+ *
+ * More information:
+ * When you issue a log statement, the logging framework iterates over each logger,
+ * and checks to see if it should forward the logMessage to the logger.
+ * This check is done using the level parameter passed to this method.
+ *
+ * For example:
+ *
+ * `[DDLog addLogger:consoleLogger withLogLevel:DDLogLevelVerbose];`
+ * `[DDLog addLogger:fileLogger withLogLevel:DDLogLevelWarning];`
+ *
+ * `DDLogError(@"oh no");` => gets forwarded to consoleLogger & fileLogger
+ * `DDLogInfo(@"hi");` => gets forwarded to consoleLogger only
+ *
+ * It is important to remember that Lumberjack uses a BITMASK.
+ * Many developers & third party frameworks may define extra log levels & flags.
+ * For example:
+ *
+ * `#define SOME_FRAMEWORK_LOG_FLAG_TRACE (1 << 6) // 0...1000000`
+ *
+ * So if you specify `DDLogLevelVerbose` to this method, you won't see the framework's trace messages.
+ *
+ * `(SOME_FRAMEWORK_LOG_FLAG_TRACE & DDLogLevelVerbose) => (01000000 & 00011111) => NO`
+ *
+ * Consider passing `DDLogLevelAll` to this method, which has all bits set.
+ * You can also use the exclusive-or bitwise operator to get a bitmask that has all flags set,
+ * except the ones you explicitly don't want. For example, if you wanted everything except verbose & debug:
+ *
+ * `((DDLogLevelAll ^ DDLogLevelVerbose) | DDLogLevelInfo)`
+ **/
++ (void)addLogger:(id <DDLogger>)logger withLevel:(DDLogLevel)level;
+
+/**
+ * Remove the logger from the system
+ */
++ (void)removeLogger:(id <DDLogger>)logger;
+
+/**
+ * Remove all the current loggers
+ */
++ (void)removeAllLoggers;
+
+/**
+ * Return all the current loggers
+ */
++ (NSArray *)allLoggers;
+
+/**
+ * Registered Dynamic Logging
+ *
+ * These methods allow you to obtain a list of classes that are using registered dynamic logging,
+ * and also provides methods to get and set their log level during run time.
+ **/
+
+/**
+ * Returns an array with the classes that are using registered dynamic logging
+ */
++ (NSArray *)registeredClasses;
+
+/**
+ * Returns an array with the classes names that are using registered dynamic logging
+ */
++ (NSArray *)registeredClassNames;
+
+/**
+ * Returns the current log level for a certain class
+ *
+ * @param aClass `Class` param
+ */
++ (DDLogLevel)levelForClass:(Class)aClass;
+
+/**
+ * Returns the current log level for a certain class
+ *
+ * @param aClassName string param
+ */
++ (DDLogLevel)levelForClassWithName:(NSString *)aClassName;
+
+/**
+ * Set the log level for a certain class
+ *
+ * @param level the new level
+ * @param aClass `Class` param
+ */
++ (void)setLevel:(DDLogLevel)level forClass:(Class)aClass;
+
+/**
+ * Set the log level for a certain class
+ *
+ * @param level the new level
+ * @param aClassName string param
+ */
++ (void)setLevel:(DDLogLevel)level forClassWithName:(NSString *)aClassName;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This protocol describes a basic logger behavior.
+ * Basically, it can log messages, store a logFormatter plus a bunch of optional behaviors.
+ * (i.e. flush, get its loggerQueue, get its name, ...
+ */
+@protocol DDLogger <NSObject>
+
+/**
+ * The log message method
+ *
+ * @param logMessage the message (model)
+ */
+- (void)logMessage:(DDLogMessage *)logMessage;
+
+/**
+ * Formatters may optionally be added to any logger.
+ *
+ * If no formatter is set, the logger simply logs the message as it is given in logMessage,
+ * or it may use its own built in formatting style.
+ **/
+@property (nonatomic, strong) id <DDLogFormatter> logFormatter;
+
+@optional
+
+/**
+ * Since logging is asynchronous, adding and removing loggers is also asynchronous.
+ * In other words, the loggers are added and removed at appropriate times with regards to log messages.
+ *
+ * - Loggers will not receive log messages that were executed prior to when they were added.
+ * - Loggers will not receive log messages that were executed after they were removed.
+ *
+ * These methods are executed in the logging thread/queue.
+ * This is the same thread/queue that will execute every logMessage: invocation.
+ * Loggers may use these methods for thread synchronization or other setup/teardown tasks.
+ **/
+- (void)didAddLogger;
+
+/**
+ * See the above description for `didAddLoger`
+ */
+- (void)willRemoveLogger;
+
+/**
+ * Some loggers may buffer IO for optimization purposes.
+ * For example, a database logger may only save occasionaly as the disk IO is slow.
+ * In such loggers, this method should be implemented to flush any pending IO.
+ *
+ * This allows invocations of DDLog's flushLog method to be propogated to loggers that need it.
+ *
+ * Note that DDLog's flushLog method is invoked automatically when the application quits,
+ * and it may be also invoked manually by the developer prior to application crashes, or other such reasons.
+ **/
+- (void)flush;
+
+/**
+ * Each logger is executed concurrently with respect to the other loggers.
+ * Thus, a dedicated dispatch queue is used for each logger.
+ * Logger implementations may optionally choose to provide their own dispatch queue.
+ **/
+@property (nonatomic, DISPATCH_QUEUE_REFERENCE_TYPE, readonly) dispatch_queue_t loggerQueue;
+
+/**
+ * If the logger implementation does not choose to provide its own queue,
+ * one will automatically be created for it.
+ * The created queue will receive its name from this method.
+ * This may be helpful for debugging or profiling reasons.
+ **/
+@property (nonatomic, readonly) NSString *loggerName;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This protocol describes the behavior of a log formatter
+ */
+@protocol DDLogFormatter <NSObject>
+@required
+
+/**
+ * Formatters may optionally be added to any logger.
+ * This allows for increased flexibility in the logging environment.
+ * For example, log messages for log files may be formatted differently than log messages for the console.
+ *
+ * For more information about formatters, see the "Custom Formatters" page:
+ * Documentation/CustomFormatters.md
+ *
+ * The formatter may also optionally filter the log message by returning nil,
+ * in which case the logger will not log the message.
+ **/
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage;
+
+@optional
+
+/**
+ * A single formatter instance can be added to multiple loggers.
+ * These methods provides hooks to notify the formatter of when it's added/removed.
+ *
+ * This is primarily for thread-safety.
+ * If a formatter is explicitly not thread-safe, it may wish to throw an exception if added to multiple loggers.
+ * Or if a formatter has potentially thread-unsafe code (e.g. NSDateFormatter),
+ * it could possibly use these hooks to switch to thread-safe versions of the code.
+ **/
+- (void)didAddToLogger:(id <DDLogger>)logger;
+
+/**
+ * See the above description for `didAddToLogger:`
+ */
+- (void)willRemoveFromLogger:(id <DDLogger>)logger;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This protocol describes a dynamic logging component
+ */
+@protocol DDRegisteredDynamicLogging
+
+/**
+ * Implement these methods to allow a file's log level to be managed from a central location.
+ *
+ * This is useful if you'd like to be able to change log levels for various parts
+ * of your code from within the running application.
+ *
+ * Imagine pulling up the settings for your application,
+ * and being able to configure the logging level on a per file basis.
+ *
+ * The implementation can be very straight-forward:
+ *
+ * ```
+ * + (int)ddLogLevel
+ * {
+ * return ddLogLevel;
+ * }
+ *
+ * + (void)ddSetLogLevel:(DDLogLevel)level
+ * {
+ * ddLogLevel = level;
+ * }
+ * ```
+ **/
++ (DDLogLevel)ddLogLevel;
+
+/**
+ * See the above description for `ddLogLevel`
+ */
++ (void)ddSetLogLevel:(DDLogLevel)level;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifndef NS_DESIGNATED_INITIALIZER
+ #define NS_DESIGNATED_INITIALIZER
+#endif
+
+/**
+ * Log message options, allow copying certain log elements
+ */
+typedef NS_OPTIONS(NSInteger, DDLogMessageOptions){
+ /**
+ * Use this to use a copy of the file path
+ */
+ DDLogMessageCopyFile = 1 << 0,
+ /**
+ * Use this to use a copy of the function name
+ */
+ DDLogMessageCopyFunction = 1 << 1
+};
+
+/**
+ * The `DDLogMessage` class encapsulates information about the log message.
+ * If you write custom loggers or formatters, you will be dealing with objects of this class.
+ **/
+@interface DDLogMessage : NSObject <NSCopying>
+{
+ // Direct accessors to be used only for performance
+ @public
+ NSString *_message;
+ DDLogLevel _level;
+ DDLogFlag _flag;
+ NSInteger _context;
+ NSString *_file;
+ NSString *_fileName;
+ NSString *_function;
+ NSUInteger _line;
+ id _tag;
+ DDLogMessageOptions _options;
+ NSDate *_timestamp;
+ NSString *_threadID;
+ NSString *_threadName;
+ NSString *_queueLabel;
+}
+
+/**
+ * Default `init` is not available
+ */
+- (instancetype)init NS_UNAVAILABLE;
+
+/**
+ * Standard init method for a log message object.
+ * Used by the logging primitives. (And the macros use the logging primitives.)
+ *
+ * If you find need to manually create logMessage objects, there is one thing you should be aware of:
+ *
+ * If no flags are passed, the method expects the file and function parameters to be string literals.
+ * That is, it expects the given strings to exist for the duration of the object's lifetime,
+ * and it expects the given strings to be immutable.
+ * In other words, it does not copy these strings, it simply points to them.
+ * This is due to the fact that __FILE__ and __FUNCTION__ are usually used to specify these parameters,
+ * so it makes sense to optimize and skip the unnecessary allocations.
+ * However, if you need them to be copied you may use the options parameter to specify this.
+ *
+ * @param message the message
+ * @param level the log level
+ * @param flag the log flag
+ * @param context the context (if any is defined)
+ * @param file the current file
+ * @param function the current function
+ * @param line the current code line
+ * @param tag potential tag
+ * @param options a bitmask which supports DDLogMessageCopyFile and DDLogMessageCopyFunction.
+ * @param timestamp the log timestamp
+ *
+ * @return a new instance of a log message model object
+ */
+- (instancetype)initWithMessage:(NSString *)message
+ level:(DDLogLevel)level
+ flag:(DDLogFlag)flag
+ context:(NSInteger)context
+ file:(NSString *)file
+ function:(NSString *)function
+ line:(NSUInteger)line
+ tag:(id)tag
+ options:(DDLogMessageOptions)options
+ timestamp:(NSDate *)timestamp NS_DESIGNATED_INITIALIZER;
+
+/**
+ * Read-only properties
+ **/
+
+/**
+ * The log message
+ */
+@property (readonly, nonatomic) NSString *message;
+@property (readonly, nonatomic) DDLogLevel level;
+@property (readonly, nonatomic) DDLogFlag flag;
+@property (readonly, nonatomic) NSInteger context;
+@property (readonly, nonatomic) NSString *file;
+@property (readonly, nonatomic) NSString *fileName;
+@property (readonly, nonatomic) NSString *function;
+@property (readonly, nonatomic) NSUInteger line;
+@property (readonly, nonatomic) id tag;
+@property (readonly, nonatomic) DDLogMessageOptions options;
+@property (readonly, nonatomic) NSDate *timestamp;
+@property (readonly, nonatomic) NSString *threadID; // ID as it appears in NSLog calculated from the machThreadID
+@property (readonly, nonatomic) NSString *threadName;
+@property (readonly, nonatomic) NSString *queueLabel;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The `DDLogger` protocol specifies that an optional formatter can be added to a logger.
+ * Most (but not all) loggers will want to support formatters.
+ *
+ * However, writting getters and setters in a thread safe manner,
+ * while still maintaining maximum speed for the logging process, is a difficult task.
+ *
+ * To do it right, the implementation of the getter/setter has strict requiremenets:
+ * - Must NOT require the `logMessage:` method to acquire a lock.
+ * - Must NOT require the `logMessage:` method to access an atomic property (also a lock of sorts).
+ *
+ * To simplify things, an abstract logger is provided that implements the getter and setter.
+ *
+ * Logger implementations may simply extend this class,
+ * and they can ACCESS THE FORMATTER VARIABLE DIRECTLY from within their `logMessage:` method!
+ **/
+@interface DDAbstractLogger : NSObject <DDLogger>
+{
+ // Direct accessors to be used only for performance
+ @public
+ id <DDLogFormatter> _logFormatter;
+ dispatch_queue_t _loggerQueue;
+}
+
+@property (nonatomic, strong) id <DDLogFormatter> logFormatter;
+@property (nonatomic, DISPATCH_QUEUE_REFERENCE_TYPE) dispatch_queue_t loggerQueue;
+
+// For thread-safety assertions
+
+/**
+ * Return YES if the current logger uses a global queue for logging
+ */
+@property (nonatomic, readonly, getter=isOnGlobalLoggingQueue) BOOL onGlobalLoggingQueue;
+
+/**
+ * Return YES if the current logger uses the internal designated queue for logging
+ */
+@property (nonatomic, readonly, getter=isOnInternalLoggerQueue) BOOL onInternalLoggerQueue;
+
+@end
+
diff --git a/cocoalumberjack/Classes/DDLog.m b/cocoalumberjack/Classes/DDLog.m
new file mode 100644
index 0000000..03c6e7c
--- /dev/null
+++ b/cocoalumberjack/Classes/DDLog.m
@@ -0,0 +1,1167 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+ #define DD_LEGACY_MACROS 0
+#endif
+
+#import "DDLog.h"
+
+#import <pthread.h>
+#import <objc/runtime.h>
+#import <mach/mach_host.h>
+#import <mach/host_info.h>
+#import <libkern/OSAtomic.h>
+#import <Availability.h>
+#if TARGET_OS_IOS
+ #import <UIKit/UIDevice.h>
+#endif
+
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// We probably shouldn't be using DDLog() statements within the DDLog implementation.
+// But we still want to leave our log statements for any future debugging,
+// and to allow other developers to trace the implementation (which is a great learning tool).
+//
+// So we use a primitive logging macro around NSLog.
+// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog.
+
+#ifndef DD_DEBUG
+ #define DD_DEBUG NO
+#endif
+
+#define NSLogDebug(frmt, ...) do{ if(DD_DEBUG) NSLog((frmt), ##__VA_ARGS__); } while(0)
+
+// Specifies the maximum queue size of the logging thread.
+//
+// Since most logging is asynchronous, its possible for rogue threads to flood the logging queue.
+// That is, to issue an abundance of log statements faster than the logging thread can keepup.
+// Typically such a scenario occurs when log statements are added haphazardly within large loops,
+// but may also be possible if relatively slow loggers are being used.
+//
+// This property caps the queue size at a given number of outstanding log statements.
+// If a thread attempts to issue a log statement when the queue is already maxed out,
+// the issuing thread will block until the queue size drops below the max again.
+
+#define LOG_MAX_QUEUE_SIZE 1000 // Should not exceed INT32_MAX
+
+// The "global logging queue" refers to [DDLog loggingQueue].
+// It is the queue that all log statements go through.
+//
+// The logging queue sets a flag via dispatch_queue_set_specific using this key.
+// We can check for this key via dispatch_get_specific() to see if we're on the "global logging queue".
+
+static void *const GlobalLoggingQueueIdentityKey = (void *)&GlobalLoggingQueueIdentityKey;
+
+@interface DDLoggerNode : NSObject
+{
+ // Direct accessors to be used only for performance
+ @public
+ id <DDLogger> _logger;
+ DDLogLevel _level;
+ dispatch_queue_t _loggerQueue;
+}
+
+@property (nonatomic, readonly) id <DDLogger> logger;
+@property (nonatomic, readonly) DDLogLevel level;
+@property (nonatomic, readonly) dispatch_queue_t loggerQueue;
+
++ (DDLoggerNode *)nodeWithLogger:(id <DDLogger>)logger
+ loggerQueue:(dispatch_queue_t)loggerQueue
+ level:(DDLogLevel)level;
+
+@end
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation DDLog
+
+// An array used to manage all the individual loggers.
+// The array is only modified on the loggingQueue/loggingThread.
+static NSMutableArray *_loggers;
+
+// All logging statements are added to the same queue to ensure FIFO operation.
+static dispatch_queue_t _loggingQueue;
+
+// Individual loggers are executed concurrently per log statement.
+// Each logger has it's own associated queue, and a dispatch group is used for synchrnoization.
+static dispatch_group_t _loggingGroup;
+
+// In order to prevent to queue from growing infinitely large,
+// a maximum size is enforced (LOG_MAX_QUEUE_SIZE).
+static dispatch_semaphore_t _queueSemaphore;
+
+// Minor optimization for uniprocessor machines
+static NSUInteger _numProcessors;
+
+/**
+ * The runtime sends initialize to each class in a program exactly one time just before the class,
+ * or any class that inherits from it, is sent its first message from within the program. (Thus the
+ * method may never be invoked if the class is not used.) The runtime sends the initialize message to
+ * classes in a thread-safe manner. Superclasses receive this message before their subclasses.
+ *
+ * This method may also be called directly (assumably by accident), hence the safety mechanism.
+ **/
++ (void)initialize {
+ static dispatch_once_t DDLogOnceToken;
+
+ dispatch_once(&DDLogOnceToken, ^{
+ _loggers = [[NSMutableArray alloc] initWithCapacity:4];
+
+ NSLogDebug(@"DDLog: Using grand central dispatch");
+
+ _loggingQueue = dispatch_queue_create("cocoa.lumberjack", NULL);
+ _loggingGroup = dispatch_group_create();
+
+ void *nonNullValue = GlobalLoggingQueueIdentityKey; // Whatever, just not null
+ dispatch_queue_set_specific(_loggingQueue, GlobalLoggingQueueIdentityKey, nonNullValue, NULL);
+
+ _queueSemaphore = dispatch_semaphore_create(LOG_MAX_QUEUE_SIZE);
+
+ // Figure out how many processors are available.
+ // This may be used later for an optimization on uniprocessor machines.
+
+ _numProcessors = MAX([NSProcessInfo processInfo].processorCount, 1);
+
+ NSLogDebug(@"DDLog: numProcessors = %@", @(_numProcessors));
+
+
+#if TARGET_OS_IOS
+ NSString *notificationName = @"UIApplicationWillTerminateNotification";
+#else
+ NSString *notificationName = nil;
+
+ // On Command Line Tool apps AppKit may not be avaliable
+#ifdef NSAppKitVersionNumber10_0
+
+ if (NSApp) {
+ notificationName = @"NSApplicationWillTerminateNotification";
+ }
+
+#endif
+
+ if (!notificationName) {
+ // If there is no NSApp -> we are running Command Line Tool app.
+ // In this case terminate notification wouldn't be fired, so we use workaround.
+ atexit_b (^{
+ [self applicationWillTerminate:nil];
+ });
+ }
+
+#endif /* if TARGET_OS_IOS */
+
+ if (notificationName) {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(applicationWillTerminate:)
+ name:notificationName
+ object:nil];
+ }
+ });
+}
+
+/**
+ * Provides access to the logging queue.
+ **/
++ (dispatch_queue_t)loggingQueue {
+ return _loggingQueue;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Notifications
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
++ (void)applicationWillTerminate:(NSNotification * __attribute__((unused)))notification {
+ [self flushLog];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Logger Management
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
++ (void)addLogger:(id <DDLogger>)logger {
+ [self addLogger:logger withLevel:DDLogLevelAll]; // DDLogLevelAll has all bits set
+}
+
++ (void)addLogger:(id <DDLogger>)logger withLevel:(DDLogLevel)level {
+ if (!logger) {
+ return;
+ }
+
+ dispatch_async(_loggingQueue, ^{ @autoreleasepool {
+ [self lt_addLogger:logger level:level];
+ } });
+}
+
++ (void)removeLogger:(id <DDLogger>)logger {
+ if (!logger) {
+ return;
+ }
+
+ dispatch_async(_loggingQueue, ^{ @autoreleasepool {
+ [self lt_removeLogger:logger];
+ } });
+}
+
++ (void)removeAllLoggers {
+ dispatch_async(_loggingQueue, ^{ @autoreleasepool {
+ [self lt_removeAllLoggers];
+ } });
+}
+
++ (NSArray *)allLoggers {
+ __block NSArray *theLoggers;
+
+ dispatch_sync(_loggingQueue, ^{ @autoreleasepool {
+ theLoggers = [self lt_allLoggers];
+ } });
+
+ return theLoggers;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - Master Logging
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
++ (void)queueLogMessage:(DDLogMessage *)logMessage asynchronously:(BOOL)asyncFlag {
+ // We have a tricky situation here...
+ //
+ // In the common case, when the queueSize is below the maximumQueueSize,
+ // we want to simply enqueue the logMessage. And we want to do this as fast as possible,
+ // which means we don't want to block and we don't want to use any locks.
+ //
+ // However, if the queueSize gets too big, we want to block.
+ // But we have very strict requirements as to when we block, and how long we block.
+ //
+ // The following example should help illustrate our requirements:
+ //
+ // Imagine that the maximum queue size is configured to be 5,
+ // and that there are already 5 log messages queued.
+ // Let us call these 5 queued log messages A, B, C, D, and E. (A is next to be executed)
+ //
+ // Now if our thread issues a log statement (let us call the log message F),
+ // it should block before the message is added to the queue.
+ // Furthermore, it should be unblocked immediately after A has been unqueued.
+ //
+ // The requirements are strict in this manner so that we block only as long as necessary,
+ // and so that blocked threads are unblocked in the order in which they were blocked.
+ //
+ // Returning to our previous example, let us assume that log messages A through E are still queued.
+ // Our aforementioned thread is blocked attempting to queue log message F.
+ // Now assume we have another separate thread that attempts to issue log message G.
+ // It should block until log messages A and B have been unqueued.
+
+
+ // We are using a counting semaphore provided by GCD.
+ // The semaphore is initialized with our LOG_MAX_QUEUE_SIZE value.
+ // Everytime we want to queue a log message we decrement this value.
+ // If the resulting value is less than zero,
+ // the semaphore function waits in FIFO order for a signal to occur before returning.
+ //
+ // A dispatch semaphore is an efficient implementation of a traditional counting semaphore.
+ // Dispatch semaphores call down to the kernel only when the calling thread needs to be blocked.
+ // If the calling semaphore does not need to block, no kernel call is made.
+
+ dispatch_semaphore_wait(_queueSemaphore, DISPATCH_TIME_FOREVER);
+
+ // We've now sure we won't overflow the queue.
+ // It is time to queue our log message.
+
+ dispatch_block_t logBlock = ^{
+ @autoreleasepool {
+ [self lt_log:logMessage];
+ }
+ };
+
+ if (asyncFlag) {
+ dispatch_async(_loggingQueue, logBlock);
+ } else {
+ dispatch_sync(_loggingQueue, logBlock);
+ }
+}
+
++ (void)log:(BOOL)asynchronous
+ level:(DDLogLevel)level
+ flag:(DDLogFlag)flag
+ context:(NSInteger)context
+ file:(const char *)file
+ function:(const char *)function
+ line:(NSUInteger)line
+ tag:(id)tag
+ format:(NSString *)format, ... {
+ va_list args;
+
+ if (format) {
+ va_start(args, format);
+
+ NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
+ [self log:asynchronous
+ message:message
+ level:level
+ flag:flag
+ context:context
+ file:file
+ function:function
+ line:line
+ tag:tag];
+
+ va_end(args);
+ }
+}
+
++ (void)log:(BOOL)asynchronous
+ level:(DDLogLevel)level
+ flag:(DDLogFlag)flag
+ context:(NSInteger)context
+ file:(const char *)file
+ function:(const char *)function
+ line:(NSUInteger)line
+ tag:(id)tag
+ format:(NSString *)format
+ args:(va_list)args {
+
+ if (format) {
+ NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
+ [self log:asynchronous
+ message:message
+ level:level
+ flag:flag
+ context:context
+ file:file
+ function:function
+ line:line
+ tag:tag];
+ }
+}
+
++ (void)log:(BOOL)asynchronous
+ message:(NSString *)message
+ level:(DDLogLevel)level
+ flag:(DDLogFlag)flag
+ context:(NSInteger)context
+ file:(const char *)file
+ function:(const char *)function
+ line:(NSUInteger)line
+ tag:(id)tag {
+
+ DDLogMessage *logMessage = [[DDLogMessage alloc] initWithMessage:message
+ level:level
+ flag:flag
+ context:context
+ file:[NSString stringWithFormat:@"%s", file]
+ function:[NSString stringWithFormat:@"%s", function]
+ line:line
+ tag:tag
+ options:(DDLogMessageOptions)0
+ timestamp:nil];
+
+ [self queueLogMessage:logMessage asynchronously:asynchronous];
+}
+
++ (void)log:(BOOL)asynchronous
+ message:(DDLogMessage *)logMessage {
+ [self queueLogMessage:logMessage asynchronously:asynchronous];
+}
+
++ (void)flushLog {
+ dispatch_sync(_loggingQueue, ^{ @autoreleasepool {
+ [self lt_flush];
+ } });
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Registered Dynamic Logging
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
++ (BOOL)isRegisteredClass:(Class)class {
+ SEL getterSel = @selector(ddLogLevel);
+ SEL setterSel = @selector(ddSetLogLevel:);
+
+#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
+
+ // Issue #6 (GoogleCode) - Crashes on iOS 4.2.1 and iPhone 4
+ //
+ // Crash caused by class_getClassMethod(2).
+ //
+ // "It's a bug with UIAccessibilitySafeCategory__NSObject so it didn't pop up until
+ // users had VoiceOver enabled [...]. I was able to work around it by searching the
+ // result of class_copyMethodList() instead of calling class_getClassMethod()"
+
+ BOOL result = NO;
+
+ unsigned int methodCount, i;
+ Method *methodList = class_copyMethodList(object_getClass(class), &methodCount);
+
+ if (methodList != NULL) {
+ BOOL getterFound = NO;
+ BOOL setterFound = NO;
+
+ for (i = 0; i < methodCount; ++i) {
+ SEL currentSel = method_getName(methodList[i]);
+
+ if (currentSel == getterSel) {
+ getterFound = YES;
+ } else if (currentSel == setterSel) {
+ setterFound = YES;
+ }
+
+ if (getterFound && setterFound) {
+ result = YES;
+ break;
+ }
+ }
+
+ free(methodList);
+ }
+
+ return result;
+
+#else /* if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR */
+
+ // Issue #24 (GitHub) - Crashing in in ARC+Simulator
+ //
+ // The method +[DDLog isRegisteredClass] will crash a project when using it with ARC + Simulator.
+ // For running in the Simulator, it needs to execute the non-iOS code.
+
+ Method getter = class_getClassMethod(class, getterSel);
+ Method setter = class_getClassMethod(class, setterSel);
+
+ if ((getter != NULL) && (setter != NULL)) {
+ return YES;
+ }
+
+ return NO;
+
+#endif /* if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR */
+}
+
++ (NSArray *)registeredClasses {
+
+ // We're going to get the list of all registered classes.
+ // The Objective-C runtime library automatically registers all the classes defined in your source code.
+ //
+ // To do this we use the following method (documented in the Objective-C Runtime Reference):
+ //
+ // int objc_getClassList(Class *buffer, int bufferLen)
+ //
+ // We can pass (NULL, 0) to obtain the total number of
+ // registered class definitions without actually retrieving any class definitions.
+ // This allows us to allocate the minimum amount of memory needed for the application.
+
+ NSUInteger numClasses = 0;
+ Class *classes = NULL;
+
+ while (numClasses == 0) {
+
+ numClasses = (NSUInteger)MAX(objc_getClassList(NULL, 0), 0);
+
+ // numClasses now tells us how many classes we have (but it might change)
+ // So we can allocate our buffer, and get pointers to all the class definitions.
+
+ NSUInteger bufferSize = numClasses;
+
+ classes = numClasses ? (Class *)malloc(sizeof(Class) * bufferSize) : NULL;
+ if (classes == NULL) {
+ return nil; //no memory or classes?
+ }
+
+ numClasses = (NSUInteger)MAX(objc_getClassList(classes, (int)bufferSize),0);
+
+ if (numClasses > bufferSize || numClasses == 0) {
+ //apparently more classes added between calls (or a problem); try again
+ free(classes);
+ numClasses = 0;
+ }
+ }
+
+ // We can now loop through the classes, and test each one to see if it is a DDLogging class.
+
+ NSMutableArray *result = [NSMutableArray arrayWithCapacity:numClasses];
+
+ for (NSUInteger i = 0; i < numClasses; i++) {
+ Class class = classes[i];
+
+ if ([self isRegisteredClass:class]) {
+ [result addObject:class];
+ }
+ }
+
+ free(classes);
+
+ return result;
+}
+
++ (NSArray *)registeredClassNames {
+ NSArray *registeredClasses = [self registeredClasses];
+ NSMutableArray *result = [NSMutableArray arrayWithCapacity:[registeredClasses count]];
+
+ for (Class class in registeredClasses) {
+ [result addObject:NSStringFromClass(class)];
+ }
+ return result;
+}
+
++ (DDLogLevel)levelForClass:(Class)aClass {
+ if ([self isRegisteredClass:aClass]) {
+ return [aClass ddLogLevel];
+ }
+ return (DDLogLevel)-1;
+}
+
++ (DDLogLevel)levelForClassWithName:(NSString *)aClassName {
+ Class aClass = NSClassFromString(aClassName);
+
+ return [self levelForClass:aClass];
+}
+
++ (void)setLevel:(DDLogLevel)level forClass:(Class)aClass {
+ if ([self isRegisteredClass:aClass]) {
+ [aClass ddSetLogLevel:level];
+ }
+}
+
++ (void)setLevel:(DDLogLevel)level forClassWithName:(NSString *)aClassName {
+ Class aClass = NSClassFromString(aClassName);
+ [self setLevel:level forClass:aClass];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Logging Thread
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
++ (void)lt_addLogger:(id <DDLogger>)logger level:(DDLogLevel)level {
+ // Add to loggers array.
+ // Need to create loggerQueue if loggerNode doesn't provide one.
+
+ NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey),
+ @"This method should only be run on the logging thread/queue");
+
+ dispatch_queue_t loggerQueue = NULL;
+
+ if ([logger respondsToSelector:@selector(loggerQueue)]) {
+ // Logger may be providing its own queue
+
+ loggerQueue = [logger loggerQueue];
+ }
+
+ if (loggerQueue == nil) {
+ // Automatically create queue for the logger.
+ // Use the logger name as the queue name if possible.
+
+ const char *loggerQueueName = NULL;
+
+ if ([logger respondsToSelector:@selector(loggerName)]) {
+ loggerQueueName = [[logger loggerName] UTF8String];
+ }
+
+ loggerQueue = dispatch_queue_create(loggerQueueName, NULL);
+ }
+
+ DDLoggerNode *loggerNode = [DDLoggerNode nodeWithLogger:logger loggerQueue:loggerQueue level:level];
+ [_loggers addObject:loggerNode];
+
+ if ([logger respondsToSelector:@selector(didAddLogger)]) {
+ dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool {
+ [logger didAddLogger];
+ } });
+ }
+}
+
++ (void)lt_removeLogger:(id <DDLogger>)logger {
+ // Find associated loggerNode in list of added loggers
+
+ NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey),
+ @"This method should only be run on the logging thread/queue");
+
+ DDLoggerNode *loggerNode = nil;
+
+ for (DDLoggerNode *node in _loggers) {
+ if (node->_logger == logger) {
+ loggerNode = node;
+ break;
+ }
+ }
+
+ if (loggerNode == nil) {
+ NSLogDebug(@"DDLog: Request to remove logger which wasn't added");
+ return;
+ }
+
+ // Notify logger
+ if ([logger respondsToSelector:@selector(willRemoveLogger)]) {
+ dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool {
+ [logger willRemoveLogger];
+ } });
+ }
+
+ // Remove from loggers array
+ [_loggers removeObject:loggerNode];
+}
+
++ (void)lt_removeAllLoggers {
+ NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey),
+ @"This method should only be run on the logging thread/queue");
+
+ // Notify all loggers
+ for (DDLoggerNode *loggerNode in _loggers) {
+ if ([loggerNode->_logger respondsToSelector:@selector(willRemoveLogger)]) {
+ dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool {
+ [loggerNode->_logger willRemoveLogger];
+ } });
+ }
+ }
+
+ // Remove all loggers from array
+
+ [_loggers removeAllObjects];
+}
+
++ (NSArray *)lt_allLoggers {
+ NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey),
+ @"This method should only be run on the logging thread/queue");
+
+ NSMutableArray *theLoggers = [NSMutableArray new];
+
+ for (DDLoggerNode *loggerNode in _loggers) {
+ [theLoggers addObject:loggerNode->_logger];
+ }
+
+ return [theLoggers copy];
+}
+
++ (void)lt_log:(DDLogMessage *)logMessage {
+ // Execute the given log message on each of our loggers.
+
+ NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey),
+ @"This method should only be run on the logging thread/queue");
+
+ if (_numProcessors > 1) {
+ // Execute each logger concurrently, each within its own queue.
+ // All blocks are added to same group.
+ // After each block has been queued, wait on group.
+ //
+ // The waiting ensures that a slow logger doesn't end up with a large queue of pending log messages.
+ // This would defeat the purpose of the efforts we made earlier to restrict the max queue size.
+
+ for (DDLoggerNode *loggerNode in _loggers) {
+ // skip the loggers that shouldn't write this message based on the log level
+
+ if (!(logMessage->_flag & loggerNode->_level)) {
+ continue;
+ }
+
+ dispatch_group_async(_loggingGroup, loggerNode->_loggerQueue, ^{ @autoreleasepool {
+ [loggerNode->_logger logMessage:logMessage];
+ } });
+ }
+
+ dispatch_group_wait(_loggingGroup, DISPATCH_TIME_FOREVER);
+ } else {
+ // Execute each logger serialy, each within its own queue.
+
+ for (DDLoggerNode *loggerNode in _loggers) {
+ // skip the loggers that shouldn't write this message based on the log level
+
+ if (!(logMessage->_flag & loggerNode->_level)) {
+ continue;
+ }
+
+ dispatch_sync(loggerNode->_loggerQueue, ^{ @autoreleasepool {
+ [loggerNode->_logger logMessage:logMessage];
+ } });
+ }
+ }
+
+ // If our queue got too big, there may be blocked threads waiting to add log messages to the queue.
+ // Since we've now dequeued an item from the log, we may need to unblock the next thread.
+
+ // We are using a counting semaphore provided by GCD.
+ // The semaphore is initialized with our LOG_MAX_QUEUE_SIZE value.
+ // When a log message is queued this value is decremented.
+ // When a log message is dequeued this value is incremented.
+ // If the value ever drops below zero,
+ // the queueing thread blocks and waits in FIFO order for us to signal it.
+ //
+ // A dispatch semaphore is an efficient implementation of a traditional counting semaphore.
+ // Dispatch semaphores call down to the kernel only when the calling thread needs to be blocked.
+ // If the calling semaphore does not need to block, no kernel call is made.
+
+ dispatch_semaphore_signal(_queueSemaphore);
+}
+
++ (void)lt_flush {
+ // All log statements issued before the flush method was invoked have now been executed.
+ //
+ // Now we need to propogate the flush request to any loggers that implement the flush method.
+ // This is designed for loggers that buffer IO.
+
+ NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey),
+ @"This method should only be run on the logging thread/queue");
+
+ for (DDLoggerNode *loggerNode in _loggers) {
+ if ([loggerNode->_logger respondsToSelector:@selector(flush)]) {
+ dispatch_group_async(_loggingGroup, loggerNode->_loggerQueue, ^{ @autoreleasepool {
+ [loggerNode->_logger flush];
+ } });
+ }
+ }
+
+ dispatch_group_wait(_loggingGroup, DISPATCH_TIME_FOREVER);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Utilities
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+NSString * DDExtractFileNameWithoutExtension(const char *filePath, BOOL copy) {
+ if (filePath == NULL) {
+ return nil;
+ }
+
+ char *lastSlash = NULL;
+ char *lastDot = NULL;
+
+ char *p = (char *)filePath;
+
+ while (*p != '\0') {
+ if (*p == '/') {
+ lastSlash = p;
+ } else if (*p == '.') {
+ lastDot = p;
+ }
+
+ p++;
+ }
+
+ char *subStr;
+ NSUInteger subLen;
+
+ if (lastSlash) {
+ if (lastDot) {
+ // lastSlash -> lastDot
+ subStr = lastSlash + 1;
+ subLen = (NSUInteger)(lastDot - subStr);
+ } else {
+ // lastSlash -> endOfString
+ subStr = lastSlash + 1;
+ subLen = (NSUInteger)(p - subStr);
+ }
+ } else {
+ if (lastDot) {
+ // startOfString -> lastDot
+ subStr = (char *)filePath;
+ subLen = (NSUInteger)(lastDot - subStr);
+ } else {
+ // startOfString -> endOfString
+ subStr = (char *)filePath;
+ subLen = (NSUInteger)(p - subStr);
+ }
+ }
+
+ if (copy) {
+ return [[NSString alloc] initWithBytes:subStr
+ length:subLen
+ encoding:NSUTF8StringEncoding];
+ } else {
+ // We can take advantage of the fact that __FILE__ is a string literal.
+ // Specifically, we don't need to waste time copying the string.
+ // We can just tell NSString to point to a range within the string literal.
+
+ return [[NSString alloc] initWithBytesNoCopy:subStr
+ length:subLen
+ encoding:NSUTF8StringEncoding
+ freeWhenDone:NO];
+ }
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation DDLoggerNode
+
+- (instancetype)initWithLogger:(id <DDLogger>)logger loggerQueue:(dispatch_queue_t)loggerQueue level:(DDLogLevel)level {
+ if ((self = [super init])) {
+ _logger = logger;
+
+ if (loggerQueue) {
+ _loggerQueue = loggerQueue;
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_retain(loggerQueue);
+ #endif
+ }
+
+ _level = level;
+ }
+ return self;
+}
+
++ (DDLoggerNode *)nodeWithLogger:(id <DDLogger>)logger loggerQueue:(dispatch_queue_t)loggerQueue level:(DDLogLevel)level {
+ return [[DDLoggerNode alloc] initWithLogger:logger loggerQueue:loggerQueue level:level];
+}
+
+- (void)dealloc {
+ #if !OS_OBJECT_USE_OBJC
+ if (_loggerQueue) {
+ dispatch_release(_loggerQueue);
+ }
+ #endif
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation DDLogMessage
+
+// Can we use DISPATCH_CURRENT_QUEUE_LABEL ?
+// Can we use dispatch_get_current_queue (without it crashing) ?
+//
+// a) Compiling against newer SDK's (iOS 7+/OS X 10.9+) where DISPATCH_CURRENT_QUEUE_LABEL is defined
+// on a (iOS 7.0+/OS X 10.9+) runtime version
+//
+// b) Systems where dispatch_get_current_queue is not yet deprecated and won't crash (< iOS 6.0/OS X 10.9)
+//
+// dispatch_get_current_queue(void);
+// __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_6,__MAC_10_9,__IPHONE_4_0,__IPHONE_6_0)
+
+#if TARGET_OS_IOS
+
+// Compiling for iOS
+
+ #define USE_DISPATCH_CURRENT_QUEUE_LABEL ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
+ #define USE_DISPATCH_GET_CURRENT_QUEUE ([[[UIDevice currentDevice] systemVersion] floatValue] >= 6.1)
+
+#elif TARGET_OS_WATCH || TARGET_OS_TV
+
+// Compiling for watchOS, tvOS
+
+#define USE_DISPATCH_CURRENT_QUEUE_LABEL YES
+#define USE_DISPATCH_GET_CURRENT_QUEUE YES
+
+#else
+
+// Compiling for Mac OS X
+
+ #ifndef MAC_OS_X_VERSION_10_9
+ #define MAC_OS_X_VERSION_10_9 1090
+ #endif
+
+ #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9 // Mac OS X 10.9 or later required
+
+ #define USE_DISPATCH_CURRENT_QUEUE_LABEL YES
+ #define USE_DISPATCH_GET_CURRENT_QUEUE NO
+
+ #else
+
+ #define USE_DISPATCH_CURRENT_QUEUE_LABEL ([NSTimer instancesRespondToSelector : @selector(tolerance)]) // OS X 10.9+
+ #define USE_DISPATCH_GET_CURRENT_QUEUE (![NSTimer instancesRespondToSelector : @selector(tolerance)]) // < OS X 10.9
+
+ #endif
+
+#endif /* if TARGET_OS_IOS */
+
+// Should we use pthread_threadid_np ?
+// With iOS 8+/OSX 10.10+ NSLog uses pthread_threadid_np instead of pthread_mach_thread_np
+
+#if TARGET_OS_IOS
+
+// Compiling for iOS
+
+ #ifndef kCFCoreFoundationVersionNumber_iOS_8_0
+ #define kCFCoreFoundationVersionNumber_iOS_8_0 1140.10
+ #endif
+
+ #define USE_PTHREAD_THREADID_NP (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0)
+
+#elif TARGET_OS_WATCH || TARGET_OS_TV
+
+// Compiling for watchOS, tvOS
+
+#define USE_PTHREAD_THREADID_NP YES
+
+#else
+
+// Compiling for Mac OS X
+
+ #ifndef kCFCoreFoundationVersionNumber10_10
+ #define kCFCoreFoundationVersionNumber10_10 1151.16
+ #endif
+
+ #define USE_PTHREAD_THREADID_NP (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber10_10)
+
+#endif /* if TARGET_OS_IOS */
+
+- (instancetype)initWithMessage:(NSString *)message
+ level:(DDLogLevel)level
+ flag:(DDLogFlag)flag
+ context:(NSInteger)context
+ file:(NSString *)file
+ function:(NSString *)function
+ line:(NSUInteger)line
+ tag:(id)tag
+ options:(DDLogMessageOptions)options
+ timestamp:(NSDate *)timestamp {
+ if ((self = [super init])) {
+ _message = [message copy];
+ _level = level;
+ _flag = flag;
+ _context = context;
+
+ BOOL copyFile = (options & DDLogMessageCopyFile) == DDLogMessageCopyFile;
+ _file = copyFile ? [file copy] : file;
+
+ BOOL copyFunction = (options & DDLogMessageCopyFunction) == DDLogMessageCopyFunction;
+ _function = copyFunction ? [function copy] : function;
+
+ _line = line;
+ _tag = tag;
+ _options = options;
+ _timestamp = timestamp ?: [NSDate new];
+
+ if (USE_PTHREAD_THREADID_NP) {
+ __uint64_t tid;
+ pthread_threadid_np(NULL, &tid);
+ _threadID = [[NSString alloc] initWithFormat:@"%llu", tid];
+ } else {
+ _threadID = [[NSString alloc] initWithFormat:@"%x", pthread_mach_thread_np(pthread_self())];
+ }
+ _threadName = NSThread.currentThread.name;
+
+ // Get the file name without extension
+ _fileName = [_file lastPathComponent];
+ NSUInteger dotLocation = [_fileName rangeOfString:@"." options:NSBackwardsSearch].location;
+ if (dotLocation != NSNotFound)
+ {
+ _fileName = [_fileName substringToIndex:dotLocation];
+ }
+
+ // Try to get the current queue's label
+ if (USE_DISPATCH_CURRENT_QUEUE_LABEL) {
+ _queueLabel = [[NSString alloc] initWithFormat:@"%s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
+ } else if (USE_DISPATCH_GET_CURRENT_QUEUE) {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ dispatch_queue_t currentQueue = dispatch_get_current_queue();
+ #pragma clang diagnostic pop
+ _queueLabel = [[NSString alloc] initWithFormat:@"%s", dispatch_queue_get_label(currentQueue)];
+ } else {
+ _queueLabel = @""; // iOS 6.x only
+ }
+ }
+ return self;
+}
+
+- (id)copyWithZone:(NSZone * __attribute__((unused)))zone {
+ DDLogMessage *newMessage = [DDLogMessage new];
+
+ newMessage->_message = _message;
+ newMessage->_level = _level;
+ newMessage->_flag = _flag;
+ newMessage->_context = _context;
+ newMessage->_file = _file;
+ newMessage->_fileName = _fileName;
+ newMessage->_function = _function;
+ newMessage->_line = _line;
+ newMessage->_tag = _tag;
+ newMessage->_options = _options;
+ newMessage->_timestamp = _timestamp;
+ newMessage->_threadID = _threadID;
+ newMessage->_threadName = _threadName;
+ newMessage->_queueLabel = _queueLabel;
+
+ return newMessage;
+}
+
+@end
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation DDAbstractLogger
+
+- (instancetype)init {
+ if ((self = [super init])) {
+ const char *loggerQueueName = NULL;
+
+ if ([self respondsToSelector:@selector(loggerName)]) {
+ loggerQueueName = [[self loggerName] UTF8String];
+ }
+
+ _loggerQueue = dispatch_queue_create(loggerQueueName, NULL);
+
+ // We're going to use dispatch_queue_set_specific() to "mark" our loggerQueue.
+ // Later we can use dispatch_get_specific() to determine if we're executing on our loggerQueue.
+ // The documentation states:
+ //
+ // > Keys are only compared as pointers and are never dereferenced.
+ // > Thus, you can use a pointer to a static variable for a specific subsystem or
+ // > any other value that allows you to identify the value uniquely.
+ // > Specifying a pointer to a string constant is not recommended.
+ //
+ // So we're going to use the very convenient key of "self",
+ // which also works when multiple logger classes extend this class, as each will have a different "self" key.
+ //
+ // This is used primarily for thread-safety assertions (via the isOnInternalLoggerQueue method below).
+
+ void *key = (__bridge void *)self;
+ void *nonNullValue = (__bridge void *)self;
+
+ dispatch_queue_set_specific(_loggerQueue, key, nonNullValue, NULL);
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ #if !OS_OBJECT_USE_OBJC
+
+ if (_loggerQueue) {
+ dispatch_release(_loggerQueue);
+ }
+
+ #endif
+}
+
+- (void)logMessage:(DDLogMessage * __attribute__((unused)))logMessage {
+ // Override me
+}
+
+- (id <DDLogFormatter>)logFormatter {
+ // This method must be thread safe and intuitive.
+ // Therefore if somebody executes the following code:
+ //
+ // [logger setLogFormatter:myFormatter];
+ // formatter = [logger logFormatter];
+ //
+ // They would expect formatter to equal myFormatter.
+ // This functionality must be ensured by the getter and setter method.
+ //
+ // The thread safety must not come at a cost to the performance of the logMessage method.
+ // This method is likely called sporadically, while the logMessage method is called repeatedly.
+ // This means, the implementation of this method:
+ // - Must NOT require the logMessage method to acquire a lock.
+ // - Must NOT require the logMessage method to access an atomic property (also a lock of sorts).
+ //
+ // Thread safety is ensured by executing access to the formatter variable on the loggerQueue.
+ // This is the same queue that the logMessage method operates on.
+ //
+ // Note: The last time I benchmarked the performance of direct access vs atomic property access,
+ // direct access was over twice as fast on the desktop and over 6 times as fast on the iPhone.
+ //
+ // Furthermore, consider the following code:
+ //
+ // DDLogVerbose(@"log msg 1");
+ // DDLogVerbose(@"log msg 2");
+ // [logger setFormatter:myFormatter];
+ // DDLogVerbose(@"log msg 3");
+ //
+ // Our intuitive requirement means that the new formatter will only apply to the 3rd log message.
+ // This must remain true even when using asynchronous logging.
+ // We must keep in mind the various queue's that are in play here:
+ //
+ // loggerQueue : Our own private internal queue that the logMessage method runs on.
+ // Operations are added to this queue from the global loggingQueue.
+ //
+ // globalLoggingQueue : The queue that all log messages go through before they arrive in our loggerQueue.
+ //
+ // All log statements go through the serial gloabalLoggingQueue before they arrive at our loggerQueue.
+ // Thus this method also goes through the serial globalLoggingQueue to ensure intuitive operation.
+
+ // IMPORTANT NOTE:
+ //
+ // Methods within the DDLogger implementation MUST access the formatter ivar directly.
+ // This method is designed explicitly for external access.
+ //
+ // Using "self." syntax to go through this method will cause immediate deadlock.
+ // This is the intended result. Fix it by accessing the ivar directly.
+ // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+ NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
+
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+
+ __block id <DDLogFormatter> result;
+
+ dispatch_sync(globalLoggingQueue, ^{
+ dispatch_sync(_loggerQueue, ^{
+ result = _logFormatter;
+ });
+ });
+
+ return result;
+}
+
+- (void)setLogFormatter:(id <DDLogFormatter>)logFormatter {
+ // The design of this method is documented extensively in the logFormatter message (above in code).
+
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+ NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
+
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ if (_logFormatter != logFormatter) {
+ if ([_logFormatter respondsToSelector:@selector(willRemoveFromLogger:)]) {
+ [_logFormatter willRemoveFromLogger:self];
+ }
+
+ _logFormatter = logFormatter;
+
+ if ([_logFormatter respondsToSelector:@selector(didAddToLogger:)]) {
+ [_logFormatter didAddToLogger:self];
+ }
+ }
+ }
+ };
+
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(_loggerQueue, block);
+ });
+}
+
+- (dispatch_queue_t)loggerQueue {
+ return _loggerQueue;
+}
+
+- (NSString *)loggerName {
+ return NSStringFromClass([self class]);
+}
+
+- (BOOL)isOnGlobalLoggingQueue {
+ return (dispatch_get_specific(GlobalLoggingQueueIdentityKey) != NULL);
+}
+
+- (BOOL)isOnInternalLoggerQueue {
+ void *key = (__bridge void *)self;
+
+ return (dispatch_get_specific(key) != NULL);
+}
+
+@end
diff --git a/cocoalumberjack/Classes/DDLogMacros.h b/cocoalumberjack/Classes/DDLogMacros.h
new file mode 100644
index 0000000..975d00a
--- /dev/null
+++ b/cocoalumberjack/Classes/DDLogMacros.h
@@ -0,0 +1,82 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+ #define DD_LEGACY_MACROS 0
+#endif
+
+#import "DDLog.h"
+
+/**
+ * The constant/variable/method responsible for controlling the current log level.
+ **/
+#ifndef LOG_LEVEL_DEF
+ #define LOG_LEVEL_DEF ddLogLevel
+#endif
+
+/**
+ * Whether async should be used by log messages, excluding error messages that are always sent sync.
+ **/
+#ifndef LOG_ASYNC_ENABLED
+ #define LOG_ASYNC_ENABLED YES
+#endif
+
+/**
+ * This is the single macro that all other macros below compile into.
+ * This big multiline macro makes all the other macros easier to read.
+ **/
+#define LOG_MACRO(isAsynchronous, lvl, flg, ctx, atag, fnct, frmt, ...) \
+ [DDLog log : isAsynchronous \
+ level : lvl \
+ flag : flg \
+ context : ctx \
+ file : __FILE__ \
+ function : fnct \
+ line : __LINE__ \
+ tag : atag \
+ format : (frmt), ## __VA_ARGS__]
+
+/**
+ * Define version of the macro that only execute if the log level is above the threshold.
+ * The compiled versions essentially look like this:
+ *
+ * if (logFlagForThisLogMsg & ddLogLevel) { execute log message }
+ *
+ * When LOG_LEVEL_DEF is defined as ddLogLevel.
+ *
+ * As shown further below, Lumberjack actually uses a bitmask as opposed to primitive log levels.
+ * This allows for a great amount of flexibility and some pretty advanced fine grained logging techniques.
+ *
+ * Note that when compiler optimizations are enabled (as they are for your release builds),
+ * the log messages above your logging threshold will automatically be compiled out.
+ *
+ * (If the compiler sees LOG_LEVEL_DEF/ddLogLevel declared as a constant, the compiler simply checks to see
+ * if the 'if' statement would execute, and if not it strips it from the binary.)
+ *
+ * We also define shorthand versions for asynchronous and synchronous logging.
+ **/
+#define LOG_MAYBE(async, lvl, flg, ctx, tag, fnct, frmt, ...) \
+ do { if(lvl & flg) LOG_MACRO(async, lvl, flg, ctx, tag, fnct, frmt, ##__VA_ARGS__); } while(0)
+
+/**
+ * Ready to use log macros with no context or tag.
+ **/
+#define DDLogError(frmt, ...) LOG_MAYBE(NO, LOG_LEVEL_DEF, DDLogFlagError, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
+#define DDLogWarn(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagWarning, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
+#define DDLogInfo(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagInfo, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
+#define DDLogDebug(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagDebug, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
+#define DDLogVerbose(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagVerbose, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
+
diff --git a/cocoalumberjack/Classes/DDTTYLogger.h b/cocoalumberjack/Classes/DDTTYLogger.h
new file mode 100644
index 0000000..c27415c
--- /dev/null
+++ b/cocoalumberjack/Classes/DDTTYLogger.h
@@ -0,0 +1,178 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+ #define DD_LEGACY_MACROS 0
+#endif
+
+#import "DDLog.h"
+
+#define LOG_CONTEXT_ALL INT_MAX
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-function"
+#if TARGET_OS_IPHONE
+ // iOS
+ #import <UIKit/UIColor.h>
+ typedef UIColor DDColor;
+ static inline DDColor* DDMakeColor(CGFloat r, CGFloat g, CGFloat b) {return [DDColor colorWithRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:1.0f];}
+#elif defined(DD_CLI) || !__has_include(<AppKit/NSColor.h>)
+ // OS X CLI
+ #import "CLIColor.h"
+ typedef CLIColor DDColor;
+ static inline DDColor* DDMakeColor(CGFloat r, CGFloat g, CGFloat b) {return [DDColor colorWithCalibratedRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:1.0f];}
+#else
+ // OS X with AppKit
+ #import <AppKit/NSColor.h>
+ typedef NSColor DDColor;
+ static inline DDColor* DDMakeColor(CGFloat r, CGFloat g, CGFloat b) {return [DDColor colorWithCalibratedRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:1.0f];}
+#endif
+#pragma clang diagnostic pop
+
+
+/**
+ * This class provides a logger for Terminal output or Xcode console output,
+ * depending on where you are running your code.
+ *
+ * As described in the "Getting Started" page,
+ * the traditional NSLog() function directs it's output to two places:
+ *
+ * - Apple System Log (so it shows up in Console.app)
+ * - StdErr (if stderr is a TTY, so log statements show up in Xcode console)
+ *
+ * To duplicate NSLog() functionality you can simply add this logger and an asl logger.
+ * However, if you instead choose to use file logging (for faster performance),
+ * you may choose to use only a file logger and a tty logger.
+ **/
+@interface DDTTYLogger : DDAbstractLogger <DDLogger>
+
+/**
+ * Singleton method
+ */
++ (instancetype)sharedInstance;
+
+/* Inherited from the DDLogger protocol:
+ *
+ * Formatters may optionally be added to any logger.
+ *
+ * If no formatter is set, the logger simply logs the message as it is given in logMessage,
+ * or it may use its own built in formatting style.
+ *
+ * More information about formatters can be found here:
+ * Documentation/CustomFormatters.md
+ *
+ * The actual implementation of these methods is inherited from DDAbstractLogger.
+
+ - (id <DDLogFormatter>)logFormatter;
+ - (void)setLogFormatter:(id <DDLogFormatter>)formatter;
+
+ */
+
+/**
+ * Want to use different colors for different log levels?
+ * Enable this property.
+ *
+ * If you run the application via the Terminal (not Xcode),
+ * the logger will map colors to xterm-256color or xterm-color (if available).
+ *
+ * Xcode does NOT natively support colors in the Xcode debugging console.
+ * You'll need to install the XcodeColors plugin to see colors in the Xcode console.
+ * https://github.com/robbiehanson/XcodeColors
+ *
+ * The default value is NO.
+ **/
+@property (readwrite, assign) BOOL colorsEnabled;
+
+/**
+ * When using a custom formatter you can set the `logMessage` method not to append
+ * `\n` character after each output. This allows for some greater flexibility with
+ * custom formatters. Default value is YES.
+ **/
+@property (nonatomic, readwrite, assign) BOOL automaticallyAppendNewlineForCustomFormatters;
+
+/**
+ * The default color set (foregroundColor, backgroundColor) is:
+ *
+ * - DDLogFlagError = (red, nil)
+ * - DDLogFlagWarning = (orange, nil)
+ *
+ * You can customize the colors however you see fit.
+ * Please note that you are passing a flag, NOT a level.
+ *
+ * GOOD : [ttyLogger setForegroundColor:pink backgroundColor:nil forFlag:DDLogFlagInfo]; // <- Good :)
+ * BAD : [ttyLogger setForegroundColor:pink backgroundColor:nil forFlag:DDLogLevelInfo]; // <- BAD! :(
+ *
+ * DDLogFlagInfo = 0...00100
+ * DDLogLevelInfo = 0...00111 <- Would match DDLogFlagInfo and DDLogFlagWarning and DDLogFlagError
+ *
+ * If you run the application within Xcode, then the XcodeColors plugin is required.
+ *
+ * If you run the application from a shell, then DDTTYLogger will automatically map the given color to
+ * the closest available color. (xterm-256color or xterm-color which have 256 and 16 supported colors respectively.)
+ *
+ * This method invokes setForegroundColor:backgroundColor:forFlag:context: and applies it to `LOG_CONTEXT_ALL`.
+ **/
+- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forFlag:(DDLogFlag)mask;
+
+/**
+ * Just like setForegroundColor:backgroundColor:flag, but allows you to specify a particular logging context.
+ *
+ * A logging context is often used to identify log messages coming from a 3rd party framework,
+ * although logging context's can be used for many different functions.
+ *
+ * Use LOG_CONTEXT_ALL to set the deafult color for all contexts that have no specific color set defined.
+ *
+ * Logging context's are explained in further detail here:
+ * Documentation/CustomContext.md
+ **/
+- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forFlag:(DDLogFlag)mask context:(NSInteger)ctxt;
+
+/**
+ * Similar to the methods above, but allows you to map DDLogMessage->tag to a particular color profile.
+ * For example, you could do something like this:
+ *
+ * static NSString *const PurpleTag = @"PurpleTag";
+ *
+ * #define DDLogPurple(frmt, ...) LOG_OBJC_TAG_MACRO(NO, 0, 0, 0, PurpleTag, frmt, ##__VA_ARGS__)
+ *
+ * And then where you configure CocoaLumberjack:
+ *
+ * purple = DDMakeColor((64/255.0), (0/255.0), (128/255.0));
+ *
+ * or any UIColor/NSColor constructor.
+ *
+ * Note: For CLI OS X projects that don't link with AppKit use CLIColor objects instead
+ *
+ * [[DDTTYLogger sharedInstance] setForegroundColor:purple backgroundColor:nil forTag:PurpleTag];
+ * [DDLog addLogger:[DDTTYLogger sharedInstance]];
+ *
+ * This would essentially give you a straight NSLog replacement that prints in purple:
+ *
+ * DDLogPurple(@"I'm a purple log message!");
+ **/
+- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forTag:(id <NSCopying>)tag;
+
+/**
+ * Clearing color profiles.
+ **/
+- (void)clearColorsForFlag:(DDLogFlag)mask;
+- (void)clearColorsForFlag:(DDLogFlag)mask context:(NSInteger)context;
+- (void)clearColorsForTag:(id <NSCopying>)tag;
+- (void)clearColorsForAllFlags;
+- (void)clearColorsForAllTags;
+- (void)clearAllColors;
+
+@end
diff --git a/cocoalumberjack/Classes/DDTTYLogger.m b/cocoalumberjack/Classes/DDTTYLogger.m
new file mode 100644
index 0000000..41592ca
--- /dev/null
+++ b/cocoalumberjack/Classes/DDTTYLogger.m
@@ -0,0 +1,1481 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import "DDTTYLogger.h"
+
+#import <unistd.h>
+#import <sys/uio.h>
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// We probably shouldn't be using DDLog() statements within the DDLog implementation.
+// But we still want to leave our log statements for any future debugging,
+// and to allow other developers to trace the implementation (which is a great learning tool).
+//
+// So we use primitive logging macros around NSLog.
+// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog.
+
+#ifndef DD_NSLOG_LEVEL
+ #define DD_NSLOG_LEVEL 2
+#endif
+
+#define NSLogError(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogWarn(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogInfo(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogDebug(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogVerbose(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 5) NSLog((frmt), ##__VA_ARGS__); } while(0)
+
+// Xcode does NOT natively support colors in the Xcode debugging console.
+// You'll need to install the XcodeColors plugin to see colors in the Xcode console.
+// https://github.com/robbiehanson/XcodeColors
+//
+// The following is documentation from the XcodeColors project:
+//
+//
+// How to apply color formatting to your log statements:
+//
+// To set the foreground color:
+// Insert the ESCAPE_SEQ into your string, followed by "fg124,12,255;" where r=124, g=12, b=255.
+//
+// To set the background color:
+// Insert the ESCAPE_SEQ into your string, followed by "bg12,24,36;" where r=12, g=24, b=36.
+//
+// To reset the foreground color (to default value):
+// Insert the ESCAPE_SEQ into your string, followed by "fg;"
+//
+// To reset the background color (to default value):
+// Insert the ESCAPE_SEQ into your string, followed by "bg;"
+//
+// To reset the foreground and background color (to default values) in one operation:
+// Insert the ESCAPE_SEQ into your string, followed by ";"
+
+#define XCODE_COLORS_ESCAPE_SEQ "\033["
+
+#define XCODE_COLORS_RESET_FG XCODE_COLORS_ESCAPE_SEQ "fg;" // Clear any foreground color
+#define XCODE_COLORS_RESET_BG XCODE_COLORS_ESCAPE_SEQ "bg;" // Clear any background color
+#define XCODE_COLORS_RESET XCODE_COLORS_ESCAPE_SEQ ";" // Clear any foreground or background color
+
+// If running in a shell, not all RGB colors will be supported.
+// In this case we automatically map to the closest available color.
+// In order to provide this mapping, we have a hard-coded set of the standard RGB values available in the shell.
+// However, not every shell is the same, and Apple likes to think different even when it comes to shell colors.
+//
+// Map to standard Terminal.app colors (1), or
+// map to standard xterm colors (0).
+
+#define MAP_TO_TERMINAL_APP_COLORS 1
+
+
+@interface DDTTYLoggerColorProfile : NSObject {
+ @public
+ DDLogFlag mask;
+ NSInteger context;
+
+ uint8_t fg_r;
+ uint8_t fg_g;
+ uint8_t fg_b;
+
+ uint8_t bg_r;
+ uint8_t bg_g;
+ uint8_t bg_b;
+
+ NSUInteger fgCodeIndex;
+ NSString *fgCodeRaw;
+
+ NSUInteger bgCodeIndex;
+ NSString *bgCodeRaw;
+
+ char fgCode[24];
+ size_t fgCodeLen;
+
+ char bgCode[24];
+ size_t bgCodeLen;
+
+ char resetCode[8];
+ size_t resetCodeLen;
+}
+
+- (instancetype)initWithForegroundColor:(DDColor *)fgColor backgroundColor:(DDColor *)bgColor flag:(DDLogFlag)mask context:(NSInteger)ctxt;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface DDTTYLogger () {
+ NSUInteger _calendarUnitFlags;
+
+ NSString *_appName;
+ char *_app;
+ size_t _appLen;
+
+ NSString *_processID;
+ char *_pid;
+ size_t _pidLen;
+
+ BOOL _colorsEnabled;
+ NSMutableArray *_colorProfilesArray;
+ NSMutableDictionary *_colorProfilesDict;
+}
+
+@end
+
+
+@implementation DDTTYLogger
+
+static BOOL isaColorTTY;
+static BOOL isaColor256TTY;
+static BOOL isaXcodeColorTTY;
+
+static NSArray *codes_fg = nil;
+static NSArray *codes_bg = nil;
+static NSArray *colors = nil;
+
+static DDTTYLogger *sharedInstance;
+
+/**
+ * Initializes the colors array, as well as the codes_fg and codes_bg arrays, for 16 color mode.
+ *
+ * This method is used when the application is running from within a shell that only supports 16 color mode.
+ * This method is not invoked if the application is running within Xcode, or via normal UI app launch.
+ **/
++ (void)initialize_colors_16 {
+ if (codes_fg || codes_bg || colors) {
+ return;
+ }
+
+ NSMutableArray *m_codes_fg = [NSMutableArray arrayWithCapacity:16];
+ NSMutableArray *m_codes_bg = [NSMutableArray arrayWithCapacity:16];
+ NSMutableArray *m_colors = [NSMutableArray arrayWithCapacity:16];
+
+ // In a standard shell only 16 colors are supported.
+ //
+ // More information about ansi escape codes can be found online.
+ // http://en.wikipedia.org/wiki/ANSI_escape_code
+
+ [m_codes_fg addObject:@"30m"]; // normal - black
+ [m_codes_fg addObject:@"31m"]; // normal - red
+ [m_codes_fg addObject:@"32m"]; // normal - green
+ [m_codes_fg addObject:@"33m"]; // normal - yellow
+ [m_codes_fg addObject:@"34m"]; // normal - blue
+ [m_codes_fg addObject:@"35m"]; // normal - magenta
+ [m_codes_fg addObject:@"36m"]; // normal - cyan
+ [m_codes_fg addObject:@"37m"]; // normal - gray
+ [m_codes_fg addObject:@"1;30m"]; // bright - darkgray
+ [m_codes_fg addObject:@"1;31m"]; // bright - red
+ [m_codes_fg addObject:@"1;32m"]; // bright - green
+ [m_codes_fg addObject:@"1;33m"]; // bright - yellow
+ [m_codes_fg addObject:@"1;34m"]; // bright - blue
+ [m_codes_fg addObject:@"1;35m"]; // bright - magenta
+ [m_codes_fg addObject:@"1;36m"]; // bright - cyan
+ [m_codes_fg addObject:@"1;37m"]; // bright - white
+
+ [m_codes_bg addObject:@"40m"]; // normal - black
+ [m_codes_bg addObject:@"41m"]; // normal - red
+ [m_codes_bg addObject:@"42m"]; // normal - green
+ [m_codes_bg addObject:@"43m"]; // normal - yellow
+ [m_codes_bg addObject:@"44m"]; // normal - blue
+ [m_codes_bg addObject:@"45m"]; // normal - magenta
+ [m_codes_bg addObject:@"46m"]; // normal - cyan
+ [m_codes_bg addObject:@"47m"]; // normal - gray
+ [m_codes_bg addObject:@"1;40m"]; // bright - darkgray
+ [m_codes_bg addObject:@"1;41m"]; // bright - red
+ [m_codes_bg addObject:@"1;42m"]; // bright - green
+ [m_codes_bg addObject:@"1;43m"]; // bright - yellow
+ [m_codes_bg addObject:@"1;44m"]; // bright - blue
+ [m_codes_bg addObject:@"1;45m"]; // bright - magenta
+ [m_codes_bg addObject:@"1;46m"]; // bright - cyan
+ [m_codes_bg addObject:@"1;47m"]; // bright - white
+
+#if MAP_TO_TERMINAL_APP_COLORS
+
+ // Standard Terminal.app colors:
+ //
+ // These are the default colors used by Apple's Terminal.app.
+
+ [m_colors addObject:DDMakeColor( 0, 0, 0)]; // normal - black
+ [m_colors addObject:DDMakeColor(194, 54, 33)]; // normal - red
+ [m_colors addObject:DDMakeColor( 37, 188, 36)]; // normal - green
+ [m_colors addObject:DDMakeColor(173, 173, 39)]; // normal - yellow
+ [m_colors addObject:DDMakeColor( 73, 46, 225)]; // normal - blue
+ [m_colors addObject:DDMakeColor(211, 56, 211)]; // normal - magenta
+ [m_colors addObject:DDMakeColor( 51, 187, 200)]; // normal - cyan
+ [m_colors addObject:DDMakeColor(203, 204, 205)]; // normal - gray
+ [m_colors addObject:DDMakeColor(129, 131, 131)]; // bright - darkgray
+ [m_colors addObject:DDMakeColor(252, 57, 31)]; // bright - red
+ [m_colors addObject:DDMakeColor( 49, 231, 34)]; // bright - green
+ [m_colors addObject:DDMakeColor(234, 236, 35)]; // bright - yellow
+ [m_colors addObject:DDMakeColor( 88, 51, 255)]; // bright - blue
+ [m_colors addObject:DDMakeColor(249, 53, 248)]; // bright - magenta
+ [m_colors addObject:DDMakeColor( 20, 240, 240)]; // bright - cyan
+ [m_colors addObject:DDMakeColor(233, 235, 235)]; // bright - white
+
+#else /* if MAP_TO_TERMINAL_APP_COLORS */
+
+ // Standard xterm colors:
+ //
+ // These are the default colors used by most xterm shells.
+
+ [m_colors addObject:DDMakeColor( 0, 0, 0)]; // normal - black
+ [m_colors addObject:DDMakeColor(205, 0, 0)]; // normal - red
+ [m_colors addObject:DDMakeColor( 0, 205, 0)]; // normal - green
+ [m_colors addObject:DDMakeColor(205, 205, 0)]; // normal - yellow
+ [m_colors addObject:DDMakeColor( 0, 0, 238)]; // normal - blue
+ [m_colors addObject:DDMakeColor(205, 0, 205)]; // normal - magenta
+ [m_colors addObject:DDMakeColor( 0, 205, 205)]; // normal - cyan
+ [m_colors addObject:DDMakeColor(229, 229, 229)]; // normal - gray
+ [m_colors addObject:DDMakeColor(127, 127, 127)]; // bright - darkgray
+ [m_colors addObject:DDMakeColor(255, 0, 0)]; // bright - red
+ [m_colors addObject:DDMakeColor( 0, 255, 0)]; // bright - green
+ [m_colors addObject:DDMakeColor(255, 255, 0)]; // bright - yellow
+ [m_colors addObject:DDMakeColor( 92, 92, 255)]; // bright - blue
+ [m_colors addObject:DDMakeColor(255, 0, 255)]; // bright - magenta
+ [m_colors addObject:DDMakeColor( 0, 255, 255)]; // bright - cyan
+ [m_colors addObject:DDMakeColor(255, 255, 255)]; // bright - white
+
+#endif /* if MAP_TO_TERMINAL_APP_COLORS */
+
+ codes_fg = [m_codes_fg copy];
+ codes_bg = [m_codes_bg copy];
+ colors = [m_colors copy];
+
+ NSAssert([codes_fg count] == [codes_bg count], @"Invalid colors/codes array(s)");
+ NSAssert([codes_fg count] == [colors count], @"Invalid colors/codes array(s)");
+}
+
+/**
+ * Initializes the colors array, as well as the codes_fg and codes_bg arrays, for 256 color mode.
+ *
+ * This method is used when the application is running from within a shell that supports 256 color mode.
+ * This method is not invoked if the application is running within Xcode, or via normal UI app launch.
+ **/
++ (void)initialize_colors_256 {
+ if (codes_fg || codes_bg || colors) {
+ return;
+ }
+
+ NSMutableArray *m_codes_fg = [NSMutableArray arrayWithCapacity:(256 - 16)];
+ NSMutableArray *m_codes_bg = [NSMutableArray arrayWithCapacity:(256 - 16)];
+ NSMutableArray *m_colors = [NSMutableArray arrayWithCapacity:(256 - 16)];
+
+ #if MAP_TO_TERMINAL_APP_COLORS
+
+ // Standard Terminal.app colors:
+ //
+ // These are the colors the Terminal.app uses in xterm-256color mode.
+ // In this mode, the terminal supports 256 different colors, specified by 256 color codes.
+ //
+ // The first 16 color codes map to the original 16 color codes supported by the earlier xterm-color mode.
+ // These are actually configurable, and thus we ignore them for the purposes of mapping,
+ // as we can't rely on them being constant. They are largely duplicated anyway.
+ //
+ // The next 216 color codes are designed to run the spectrum, with several shades of every color.
+ // While the color codes are standardized, the actual RGB values for each color code is not.
+ // Apple's Terminal.app uses different RGB values from that of a standard xterm.
+ // Apple's choices in colors are designed to be a little nicer on the eyes.
+ //
+ // The last 24 color codes represent a grayscale.
+ //
+ // Unfortunately, unlike the standard xterm color chart,
+ // Apple's RGB values cannot be calculated using a simple formula (at least not that I know of).
+ // Also, I don't know of any ways to programmatically query the shell for the RGB values.
+ // So this big giant color chart had to be made by hand.
+ //
+ // More information about ansi escape codes can be found online.
+ // http://en.wikipedia.org/wiki/ANSI_escape_code
+
+ // Colors
+
+ [m_colors addObject:DDMakeColor( 47, 49, 49)];
+ [m_colors addObject:DDMakeColor( 60, 42, 144)];
+ [m_colors addObject:DDMakeColor( 66, 44, 183)];
+ [m_colors addObject:DDMakeColor( 73, 46, 222)];
+ [m_colors addObject:DDMakeColor( 81, 50, 253)];
+ [m_colors addObject:DDMakeColor( 88, 51, 255)];
+
+ [m_colors addObject:DDMakeColor( 42, 128, 37)];
+ [m_colors addObject:DDMakeColor( 42, 127, 128)];
+ [m_colors addObject:DDMakeColor( 44, 126, 169)];
+ [m_colors addObject:DDMakeColor( 56, 125, 209)];
+ [m_colors addObject:DDMakeColor( 59, 124, 245)];
+ [m_colors addObject:DDMakeColor( 66, 123, 255)];
+
+ [m_colors addObject:DDMakeColor( 51, 163, 41)];
+ [m_colors addObject:DDMakeColor( 39, 162, 121)];
+ [m_colors addObject:DDMakeColor( 42, 161, 162)];
+ [m_colors addObject:DDMakeColor( 53, 160, 202)];
+ [m_colors addObject:DDMakeColor( 45, 159, 240)];
+ [m_colors addObject:DDMakeColor( 58, 158, 255)];
+
+ [m_colors addObject:DDMakeColor( 31, 196, 37)];
+ [m_colors addObject:DDMakeColor( 48, 196, 115)];
+ [m_colors addObject:DDMakeColor( 39, 195, 155)];
+ [m_colors addObject:DDMakeColor( 49, 195, 195)];
+ [m_colors addObject:DDMakeColor( 32, 194, 235)];
+ [m_colors addObject:DDMakeColor( 53, 193, 255)];
+
+ [m_colors addObject:DDMakeColor( 50, 229, 35)];
+ [m_colors addObject:DDMakeColor( 40, 229, 109)];
+ [m_colors addObject:DDMakeColor( 27, 229, 149)];
+ [m_colors addObject:DDMakeColor( 49, 228, 189)];
+ [m_colors addObject:DDMakeColor( 33, 228, 228)];
+ [m_colors addObject:DDMakeColor( 53, 227, 255)];
+
+ [m_colors addObject:DDMakeColor( 27, 254, 30)];
+ [m_colors addObject:DDMakeColor( 30, 254, 103)];
+ [m_colors addObject:DDMakeColor( 45, 254, 143)];
+ [m_colors addObject:DDMakeColor( 38, 253, 182)];
+ [m_colors addObject:DDMakeColor( 38, 253, 222)];
+ [m_colors addObject:DDMakeColor( 42, 253, 252)];
+
+ [m_colors addObject:DDMakeColor(140, 48, 40)];
+ [m_colors addObject:DDMakeColor(136, 51, 136)];
+ [m_colors addObject:DDMakeColor(135, 52, 177)];
+ [m_colors addObject:DDMakeColor(134, 52, 217)];
+ [m_colors addObject:DDMakeColor(135, 56, 248)];
+ [m_colors addObject:DDMakeColor(134, 53, 255)];
+
+ [m_colors addObject:DDMakeColor(125, 125, 38)];
+ [m_colors addObject:DDMakeColor(124, 125, 125)];
+ [m_colors addObject:DDMakeColor(122, 124, 166)];
+ [m_colors addObject:DDMakeColor(123, 124, 207)];
+ [m_colors addObject:DDMakeColor(123, 122, 247)];
+ [m_colors addObject:DDMakeColor(124, 121, 255)];
+
+ [m_colors addObject:DDMakeColor(119, 160, 35)];
+ [m_colors addObject:DDMakeColor(117, 160, 120)];
+ [m_colors addObject:DDMakeColor(117, 160, 160)];
+ [m_colors addObject:DDMakeColor(115, 159, 201)];
+ [m_colors addObject:DDMakeColor(116, 158, 240)];
+ [m_colors addObject:DDMakeColor(117, 157, 255)];
+
+ [m_colors addObject:DDMakeColor(113, 195, 39)];
+ [m_colors addObject:DDMakeColor(110, 194, 114)];
+ [m_colors addObject:DDMakeColor(111, 194, 154)];
+ [m_colors addObject:DDMakeColor(108, 194, 194)];
+ [m_colors addObject:DDMakeColor(109, 193, 234)];
+ [m_colors addObject:DDMakeColor(108, 192, 255)];
+
+ [m_colors addObject:DDMakeColor(105, 228, 30)];
+ [m_colors addObject:DDMakeColor(103, 228, 109)];
+ [m_colors addObject:DDMakeColor(105, 228, 148)];
+ [m_colors addObject:DDMakeColor(100, 227, 188)];
+ [m_colors addObject:DDMakeColor( 99, 227, 227)];
+ [m_colors addObject:DDMakeColor( 99, 226, 253)];
+
+ [m_colors addObject:DDMakeColor( 92, 253, 34)];
+ [m_colors addObject:DDMakeColor( 96, 253, 103)];
+ [m_colors addObject:DDMakeColor( 97, 253, 142)];
+ [m_colors addObject:DDMakeColor( 88, 253, 182)];
+ [m_colors addObject:DDMakeColor( 93, 253, 221)];
+ [m_colors addObject:DDMakeColor( 88, 254, 251)];
+
+ [m_colors addObject:DDMakeColor(177, 53, 34)];
+ [m_colors addObject:DDMakeColor(174, 54, 131)];
+ [m_colors addObject:DDMakeColor(172, 55, 172)];
+ [m_colors addObject:DDMakeColor(171, 57, 213)];
+ [m_colors addObject:DDMakeColor(170, 55, 249)];
+ [m_colors addObject:DDMakeColor(170, 57, 255)];
+
+ [m_colors addObject:DDMakeColor(165, 123, 37)];
+ [m_colors addObject:DDMakeColor(163, 123, 123)];
+ [m_colors addObject:DDMakeColor(162, 123, 164)];
+ [m_colors addObject:DDMakeColor(161, 122, 205)];
+ [m_colors addObject:DDMakeColor(161, 121, 241)];
+ [m_colors addObject:DDMakeColor(161, 121, 255)];
+
+ [m_colors addObject:DDMakeColor(158, 159, 33)];
+ [m_colors addObject:DDMakeColor(157, 158, 118)];
+ [m_colors addObject:DDMakeColor(157, 158, 159)];
+ [m_colors addObject:DDMakeColor(155, 157, 199)];
+ [m_colors addObject:DDMakeColor(155, 157, 239)];
+ [m_colors addObject:DDMakeColor(154, 156, 255)];
+
+ [m_colors addObject:DDMakeColor(152, 193, 40)];
+ [m_colors addObject:DDMakeColor(151, 193, 113)];
+ [m_colors addObject:DDMakeColor(150, 193, 153)];
+ [m_colors addObject:DDMakeColor(150, 192, 193)];
+ [m_colors addObject:DDMakeColor(148, 192, 232)];
+ [m_colors addObject:DDMakeColor(149, 191, 253)];
+
+ [m_colors addObject:DDMakeColor(146, 227, 28)];
+ [m_colors addObject:DDMakeColor(144, 227, 108)];
+ [m_colors addObject:DDMakeColor(144, 227, 147)];
+ [m_colors addObject:DDMakeColor(144, 227, 187)];
+ [m_colors addObject:DDMakeColor(142, 226, 227)];
+ [m_colors addObject:DDMakeColor(142, 225, 252)];
+
+ [m_colors addObject:DDMakeColor(138, 253, 36)];
+ [m_colors addObject:DDMakeColor(137, 253, 102)];
+ [m_colors addObject:DDMakeColor(136, 253, 141)];
+ [m_colors addObject:DDMakeColor(138, 254, 181)];
+ [m_colors addObject:DDMakeColor(135, 255, 220)];
+ [m_colors addObject:DDMakeColor(133, 255, 250)];
+
+ [m_colors addObject:DDMakeColor(214, 57, 30)];
+ [m_colors addObject:DDMakeColor(211, 59, 126)];
+ [m_colors addObject:DDMakeColor(209, 57, 168)];
+ [m_colors addObject:DDMakeColor(208, 55, 208)];
+ [m_colors addObject:DDMakeColor(207, 58, 247)];
+ [m_colors addObject:DDMakeColor(206, 61, 255)];
+
+ [m_colors addObject:DDMakeColor(204, 121, 32)];
+ [m_colors addObject:DDMakeColor(202, 121, 121)];
+ [m_colors addObject:DDMakeColor(201, 121, 161)];
+ [m_colors addObject:DDMakeColor(200, 120, 202)];
+ [m_colors addObject:DDMakeColor(200, 120, 241)];
+ [m_colors addObject:DDMakeColor(198, 119, 255)];
+
+ [m_colors addObject:DDMakeColor(198, 157, 37)];
+ [m_colors addObject:DDMakeColor(196, 157, 116)];
+ [m_colors addObject:DDMakeColor(195, 156, 157)];
+ [m_colors addObject:DDMakeColor(195, 156, 197)];
+ [m_colors addObject:DDMakeColor(194, 155, 236)];
+ [m_colors addObject:DDMakeColor(193, 155, 255)];
+
+ [m_colors addObject:DDMakeColor(191, 192, 36)];
+ [m_colors addObject:DDMakeColor(190, 191, 112)];
+ [m_colors addObject:DDMakeColor(189, 191, 152)];
+ [m_colors addObject:DDMakeColor(189, 191, 191)];
+ [m_colors addObject:DDMakeColor(188, 190, 230)];
+ [m_colors addObject:DDMakeColor(187, 190, 253)];
+
+ [m_colors addObject:DDMakeColor(185, 226, 28)];
+ [m_colors addObject:DDMakeColor(184, 226, 106)];
+ [m_colors addObject:DDMakeColor(183, 225, 146)];
+ [m_colors addObject:DDMakeColor(183, 225, 186)];
+ [m_colors addObject:DDMakeColor(182, 225, 225)];
+ [m_colors addObject:DDMakeColor(181, 224, 252)];
+
+ [m_colors addObject:DDMakeColor(178, 255, 35)];
+ [m_colors addObject:DDMakeColor(178, 255, 101)];
+ [m_colors addObject:DDMakeColor(177, 254, 141)];
+ [m_colors addObject:DDMakeColor(176, 254, 180)];
+ [m_colors addObject:DDMakeColor(176, 254, 220)];
+ [m_colors addObject:DDMakeColor(175, 253, 249)];
+
+ [m_colors addObject:DDMakeColor(247, 56, 30)];
+ [m_colors addObject:DDMakeColor(245, 57, 122)];
+ [m_colors addObject:DDMakeColor(243, 59, 163)];
+ [m_colors addObject:DDMakeColor(244, 60, 204)];
+ [m_colors addObject:DDMakeColor(242, 59, 241)];
+ [m_colors addObject:DDMakeColor(240, 55, 255)];
+
+ [m_colors addObject:DDMakeColor(241, 119, 36)];
+ [m_colors addObject:DDMakeColor(240, 120, 118)];
+ [m_colors addObject:DDMakeColor(238, 119, 158)];
+ [m_colors addObject:DDMakeColor(237, 119, 199)];
+ [m_colors addObject:DDMakeColor(237, 118, 238)];
+ [m_colors addObject:DDMakeColor(236, 118, 255)];
+
+ [m_colors addObject:DDMakeColor(235, 154, 36)];
+ [m_colors addObject:DDMakeColor(235, 154, 114)];
+ [m_colors addObject:DDMakeColor(234, 154, 154)];
+ [m_colors addObject:DDMakeColor(232, 154, 194)];
+ [m_colors addObject:DDMakeColor(232, 153, 234)];
+ [m_colors addObject:DDMakeColor(232, 153, 255)];
+
+ [m_colors addObject:DDMakeColor(230, 190, 30)];
+ [m_colors addObject:DDMakeColor(229, 189, 110)];
+ [m_colors addObject:DDMakeColor(228, 189, 150)];
+ [m_colors addObject:DDMakeColor(227, 189, 190)];
+ [m_colors addObject:DDMakeColor(227, 189, 229)];
+ [m_colors addObject:DDMakeColor(226, 188, 255)];
+
+ [m_colors addObject:DDMakeColor(224, 224, 35)];
+ [m_colors addObject:DDMakeColor(223, 224, 105)];
+ [m_colors addObject:DDMakeColor(222, 224, 144)];
+ [m_colors addObject:DDMakeColor(222, 223, 184)];
+ [m_colors addObject:DDMakeColor(222, 223, 224)];
+ [m_colors addObject:DDMakeColor(220, 223, 253)];
+
+ [m_colors addObject:DDMakeColor(217, 253, 28)];
+ [m_colors addObject:DDMakeColor(217, 253, 99)];
+ [m_colors addObject:DDMakeColor(216, 252, 139)];
+ [m_colors addObject:DDMakeColor(216, 252, 179)];
+ [m_colors addObject:DDMakeColor(215, 252, 218)];
+ [m_colors addObject:DDMakeColor(215, 251, 250)];
+
+ [m_colors addObject:DDMakeColor(255, 61, 30)];
+ [m_colors addObject:DDMakeColor(255, 60, 118)];
+ [m_colors addObject:DDMakeColor(255, 58, 159)];
+ [m_colors addObject:DDMakeColor(255, 56, 199)];
+ [m_colors addObject:DDMakeColor(255, 55, 238)];
+ [m_colors addObject:DDMakeColor(255, 59, 255)];
+
+ [m_colors addObject:DDMakeColor(255, 117, 29)];
+ [m_colors addObject:DDMakeColor(255, 117, 115)];
+ [m_colors addObject:DDMakeColor(255, 117, 155)];
+ [m_colors addObject:DDMakeColor(255, 117, 195)];
+ [m_colors addObject:DDMakeColor(255, 116, 235)];
+ [m_colors addObject:DDMakeColor(254, 116, 255)];
+
+ [m_colors addObject:DDMakeColor(255, 152, 27)];
+ [m_colors addObject:DDMakeColor(255, 152, 111)];
+ [m_colors addObject:DDMakeColor(254, 152, 152)];
+ [m_colors addObject:DDMakeColor(255, 152, 192)];
+ [m_colors addObject:DDMakeColor(254, 151, 231)];
+ [m_colors addObject:DDMakeColor(253, 151, 253)];
+
+ [m_colors addObject:DDMakeColor(255, 187, 33)];
+ [m_colors addObject:DDMakeColor(253, 187, 107)];
+ [m_colors addObject:DDMakeColor(252, 187, 148)];
+ [m_colors addObject:DDMakeColor(253, 187, 187)];
+ [m_colors addObject:DDMakeColor(254, 187, 227)];
+ [m_colors addObject:DDMakeColor(252, 186, 252)];
+
+ [m_colors addObject:DDMakeColor(252, 222, 34)];
+ [m_colors addObject:DDMakeColor(251, 222, 103)];
+ [m_colors addObject:DDMakeColor(251, 222, 143)];
+ [m_colors addObject:DDMakeColor(250, 222, 182)];
+ [m_colors addObject:DDMakeColor(251, 221, 222)];
+ [m_colors addObject:DDMakeColor(252, 221, 252)];
+
+ [m_colors addObject:DDMakeColor(251, 252, 15)];
+ [m_colors addObject:DDMakeColor(251, 252, 97)];
+ [m_colors addObject:DDMakeColor(249, 252, 137)];
+ [m_colors addObject:DDMakeColor(247, 252, 177)];
+ [m_colors addObject:DDMakeColor(247, 253, 217)];
+ [m_colors addObject:DDMakeColor(254, 255, 255)];
+
+ // Grayscale
+
+ [m_colors addObject:DDMakeColor( 52, 53, 53)];
+ [m_colors addObject:DDMakeColor( 57, 58, 59)];
+ [m_colors addObject:DDMakeColor( 66, 67, 67)];
+ [m_colors addObject:DDMakeColor( 75, 76, 76)];
+ [m_colors addObject:DDMakeColor( 83, 85, 85)];
+ [m_colors addObject:DDMakeColor( 92, 93, 94)];
+
+ [m_colors addObject:DDMakeColor(101, 102, 102)];
+ [m_colors addObject:DDMakeColor(109, 111, 111)];
+ [m_colors addObject:DDMakeColor(118, 119, 119)];
+ [m_colors addObject:DDMakeColor(126, 127, 128)];
+ [m_colors addObject:DDMakeColor(134, 136, 136)];
+ [m_colors addObject:DDMakeColor(143, 144, 145)];
+
+ [m_colors addObject:DDMakeColor(151, 152, 153)];
+ [m_colors addObject:DDMakeColor(159, 161, 161)];
+ [m_colors addObject:DDMakeColor(167, 169, 169)];
+ [m_colors addObject:DDMakeColor(176, 177, 177)];
+ [m_colors addObject:DDMakeColor(184, 185, 186)];
+ [m_colors addObject:DDMakeColor(192, 193, 194)];
+
+ [m_colors addObject:DDMakeColor(200, 201, 202)];
+ [m_colors addObject:DDMakeColor(208, 209, 210)];
+ [m_colors addObject:DDMakeColor(216, 218, 218)];
+ [m_colors addObject:DDMakeColor(224, 226, 226)];
+ [m_colors addObject:DDMakeColor(232, 234, 234)];
+ [m_colors addObject:DDMakeColor(240, 242, 242)];
+
+ // Color codes
+
+ int index = 16;
+
+ while (index < 256) {
+ [m_codes_fg addObject:[NSString stringWithFormat:@"38;5;%dm", index]];
+ [m_codes_bg addObject:[NSString stringWithFormat:@"48;5;%dm", index]];
+
+ index++;
+ }
+
+ #else /* if MAP_TO_TERMINAL_APP_COLORS */
+
+ // Standard xterm colors:
+ //
+ // These are the colors xterm shells use in xterm-256color mode.
+ // In this mode, the shell supports 256 different colors, specified by 256 color codes.
+ //
+ // The first 16 color codes map to the original 16 color codes supported by the earlier xterm-color mode.
+ // These are generally configurable, and thus we ignore them for the purposes of mapping,
+ // as we can't rely on them being constant. They are largely duplicated anyway.
+ //
+ // The next 216 color codes are designed to run the spectrum, with several shades of every color.
+ // The last 24 color codes represent a grayscale.
+ //
+ // While the color codes are standardized, the actual RGB values for each color code is not.
+ // However most standard xterms follow a well known color chart,
+ // which can easily be calculated using the simple formula below.
+ //
+ // More information about ansi escape codes can be found online.
+ // http://en.wikipedia.org/wiki/ANSI_escape_code
+
+ int index = 16;
+
+ int r; // red
+ int g; // green
+ int b; // blue
+
+ int ri; // r increment
+ int gi; // g increment
+ int bi; // b increment
+
+ // Calculate xterm colors (using standard algorithm)
+
+ int r = 0;
+ int g = 0;
+ int b = 0;
+
+ for (ri = 0; ri < 6; ri++) {
+ r = (ri == 0) ? 0 : 95 + (40 * (ri - 1));
+
+ for (gi = 0; gi < 6; gi++) {
+ g = (gi == 0) ? 0 : 95 + (40 * (gi - 1));
+
+ for (bi = 0; bi < 6; bi++) {
+ b = (bi == 0) ? 0 : 95 + (40 * (bi - 1));
+
+ [m_codes_fg addObject:[NSString stringWithFormat:@"38;5;%dm", index]];
+ [m_codes_bg addObject:[NSString stringWithFormat:@"48;5;%dm", index]];
+ [m_colors addObject:DDMakeColor(r, g, b)];
+
+ index++;
+ }
+ }
+ }
+
+ // Calculate xterm grayscale (using standard algorithm)
+
+ r = 8;
+ g = 8;
+ b = 8;
+
+ while (index < 256) {
+ [m_codes_fg addObject:[NSString stringWithFormat:@"38;5;%dm", index]];
+ [m_codes_bg addObject:[NSString stringWithFormat:@"48;5;%dm", index]];
+ [m_colors addObject:DDMakeColor(r, g, b)];
+
+ r += 10;
+ g += 10;
+ b += 10;
+
+ index++;
+ }
+
+ #endif /* if MAP_TO_TERMINAL_APP_COLORS */
+
+ codes_fg = [m_codes_fg copy];
+ codes_bg = [m_codes_bg copy];
+ colors = [m_colors copy];
+
+ NSAssert([codes_fg count] == [codes_bg count], @"Invalid colors/codes array(s)");
+ NSAssert([codes_fg count] == [colors count], @"Invalid colors/codes array(s)");
+}
+
++ (void)getRed:(CGFloat *)rPtr green:(CGFloat *)gPtr blue:(CGFloat *)bPtr fromColor:(DDColor *)color {
+ #if TARGET_OS_IPHONE
+
+ // iOS
+
+ BOOL done = NO;
+
+ if ([color respondsToSelector:@selector(getRed:green:blue:alpha:)]) {
+ done = [color getRed:rPtr green:gPtr blue:bPtr alpha:NULL];
+ }
+
+ if (!done) {
+ // The method getRed:green:blue:alpha: was only available starting iOS 5.
+ // So in iOS 4 and earlier, we have to jump through hoops.
+
+ CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
+
+ unsigned char pixel[4];
+ CGContextRef context = CGBitmapContextCreate(&pixel, 1, 1, 8, 4, rgbColorSpace, (CGBitmapInfo)(kCGBitmapAlphaInfoMask & kCGImageAlphaNoneSkipLast));
+
+ CGContextSetFillColorWithColor(context, [color CGColor]);
+ CGContextFillRect(context, CGRectMake(0, 0, 1, 1));
+
+ if (rPtr) {
+ *rPtr = pixel[0] / 255.0f;
+ }
+
+ if (gPtr) {
+ *gPtr = pixel[1] / 255.0f;
+ }
+
+ if (bPtr) {
+ *bPtr = pixel[2] / 255.0f;
+ }
+
+ CGContextRelease(context);
+ CGColorSpaceRelease(rgbColorSpace);
+ }
+
+ #elif defined(DD_CLI) || !__has_include(<AppKit/NSColor.h>)
+
+ // OS X without AppKit
+
+ [color getRed:rPtr green:gPtr blue:bPtr alpha:NULL];
+
+ #else /* if TARGET_OS_IPHONE */
+
+ // OS X with AppKit
+
+ NSColor *safeColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
+
+ [safeColor getRed:rPtr green:gPtr blue:bPtr alpha:NULL];
+ #endif /* if TARGET_OS_IPHONE */
+}
+
+/**
+ * Maps the given color to the closest available color supported by the shell.
+ * The shell may support 256 colors, or only 16.
+ *
+ * This method loops through the known supported color set, and calculates the closest color.
+ * The array index of that color, within the colors array, is then returned.
+ * This array index may also be used as the index within the codes_fg and codes_bg arrays.
+ **/
++ (NSUInteger)codeIndexForColor:(DDColor *)inColor {
+ CGFloat inR, inG, inB;
+
+ [self getRed:&inR green:&inG blue:&inB fromColor:inColor];
+
+ NSUInteger bestIndex = 0;
+ CGFloat lowestDistance = 100.0f;
+
+ NSUInteger i = 0;
+
+ for (DDColor *color in colors) {
+ // Calculate Euclidean distance (lower value means closer to given color)
+
+ CGFloat r, g, b;
+ [self getRed:&r green:&g blue:&b fromColor:color];
+
+ #if CGFLOAT_IS_DOUBLE
+ CGFloat distance = sqrt(pow(r - inR, 2.0) + pow(g - inG, 2.0) + pow(b - inB, 2.0));
+ #else
+ CGFloat distance = sqrtf(powf(r - inR, 2.0f) + powf(g - inG, 2.0f) + powf(b - inB, 2.0f));
+ #endif
+
+ NSLogVerbose(@"DDTTYLogger: %3lu : %.3f,%.3f,%.3f & %.3f,%.3f,%.3f = %.6f",
+ (unsigned long)i, inR, inG, inB, r, g, b, distance);
+
+ if (distance < lowestDistance) {
+ bestIndex = i;
+ lowestDistance = distance;
+
+ NSLogVerbose(@"DDTTYLogger: New best index = %lu", (unsigned long)bestIndex);
+ }
+
+ i++;
+ }
+
+ return bestIndex;
+}
+
++ (instancetype)sharedInstance {
+ static dispatch_once_t DDTTYLoggerOnceToken;
+
+ dispatch_once(&DDTTYLoggerOnceToken, ^{
+ // Xcode does NOT natively support colors in the Xcode debugging console.
+ // You'll need to install the XcodeColors plugin to see colors in the Xcode console.
+ //
+ // PS - Please read the header file before diving into the source code.
+
+ char *xcode_colors = getenv("XcodeColors");
+ char *term = getenv("TERM");
+
+ if (xcode_colors && (strcmp(xcode_colors, "YES") == 0)) {
+ isaXcodeColorTTY = YES;
+ } else if (term) {
+ if (strcasestr(term, "color") != NULL) {
+ isaColorTTY = YES;
+ isaColor256TTY = (strcasestr(term, "256") != NULL);
+
+ if (isaColor256TTY) {
+ [self initialize_colors_256];
+ } else {
+ [self initialize_colors_16];
+ }
+ }
+ }
+
+ NSLogInfo(@"DDTTYLogger: isaColorTTY = %@", (isaColorTTY ? @"YES" : @"NO"));
+ NSLogInfo(@"DDTTYLogger: isaColor256TTY: %@", (isaColor256TTY ? @"YES" : @"NO"));
+ NSLogInfo(@"DDTTYLogger: isaXcodeColorTTY: %@", (isaXcodeColorTTY ? @"YES" : @"NO"));
+
+ sharedInstance = [[[self class] alloc] init];
+ });
+
+ return sharedInstance;
+}
+
+- (instancetype)init {
+ if (sharedInstance != nil) {
+ return nil;
+ }
+
+ if ((self = [super init])) {
+ _calendarUnitFlags = (NSCalendarUnitYear |
+ NSCalendarUnitMonth |
+ NSCalendarUnitDay |
+ NSCalendarUnitHour |
+ NSCalendarUnitMinute |
+ NSCalendarUnitSecond);
+
+ // Initialze 'app' variable (char *)
+
+ _appName = [[NSProcessInfo processInfo] processName];
+
+ _appLen = [_appName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+
+ if (_appLen == 0) {
+ _appName = @"<UnnamedApp>";
+ _appLen = [_appName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+ }
+
+ _app = (char *)malloc(_appLen + 1);
+
+ if (_app == NULL) {
+ return nil;
+ }
+
+ BOOL processedAppName = [_appName getCString:_app maxLength:(_appLen + 1) encoding:NSUTF8StringEncoding];
+
+ if (NO == processedAppName) {
+ free(_app);
+ return nil;
+ }
+
+ // Initialize 'pid' variable (char *)
+
+ _processID = [NSString stringWithFormat:@"%i", (int)getpid()];
+
+ _pidLen = [_processID lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+ _pid = (char *)malloc(_pidLen + 1);
+
+ if (_pid == NULL) {
+ free(_app);
+ return nil;
+ }
+
+ BOOL processedID = [_processID getCString:_pid maxLength:(_pidLen + 1) encoding:NSUTF8StringEncoding];
+
+ if (NO == processedID) {
+ free(_app);
+ free(_pid);
+ return nil;
+ }
+
+ // Initialize color stuff
+
+ _colorsEnabled = NO;
+ _colorProfilesArray = [[NSMutableArray alloc] initWithCapacity:8];
+ _colorProfilesDict = [[NSMutableDictionary alloc] initWithCapacity:8];
+
+ _automaticallyAppendNewlineForCustomFormatters = YES;
+ }
+
+ return self;
+}
+
+- (void)loadDefaultColorProfiles {
+ [self setForegroundColor:DDMakeColor(214, 57, 30) backgroundColor:nil forFlag:DDLogFlagError];
+ [self setForegroundColor:DDMakeColor(204, 121, 32) backgroundColor:nil forFlag:DDLogFlagWarning];
+}
+
+- (BOOL)colorsEnabled {
+ // The design of this method is taken from the DDAbstractLogger implementation.
+ // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+ // Note: The internal implementation MUST access the colorsEnabled variable directly,
+ // This method is designed explicitly for external access.
+ //
+ // Using "self." syntax to go through this method will cause immediate deadlock.
+ // This is the intended result. Fix it by accessing the ivar directly.
+ // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+ NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
+
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+
+ __block BOOL result;
+
+ dispatch_sync(globalLoggingQueue, ^{
+ dispatch_sync(self.loggerQueue, ^{
+ result = _colorsEnabled;
+ });
+ });
+
+ return result;
+}
+
+- (void)setColorsEnabled:(BOOL)newColorsEnabled {
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ _colorsEnabled = newColorsEnabled;
+
+ if ([_colorProfilesArray count] == 0) {
+ [self loadDefaultColorProfiles];
+ }
+ }
+ };
+
+ // The design of this method is taken from the DDAbstractLogger implementation.
+ // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+ // Note: The internal implementation MUST access the colorsEnabled variable directly,
+ // This method is designed explicitly for external access.
+ //
+ // Using "self." syntax to go through this method will cause immediate deadlock.
+ // This is the intended result. Fix it by accessing the ivar directly.
+ // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+ NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
+
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+}
+
+- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forFlag:(DDLogFlag)mask {
+ [self setForegroundColor:txtColor backgroundColor:bgColor forFlag:mask context:LOG_CONTEXT_ALL];
+}
+
+- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forFlag:(DDLogFlag)mask context:(NSInteger)ctxt {
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ DDTTYLoggerColorProfile *newColorProfile =
+ [[DDTTYLoggerColorProfile alloc] initWithForegroundColor:txtColor
+ backgroundColor:bgColor
+ flag:mask
+ context:ctxt];
+
+ NSLogInfo(@"DDTTYLogger: newColorProfile: %@", newColorProfile);
+
+ NSUInteger i = 0;
+
+ for (DDTTYLoggerColorProfile *colorProfile in _colorProfilesArray) {
+ if ((colorProfile->mask == mask) && (colorProfile->context == ctxt)) {
+ break;
+ }
+
+ i++;
+ }
+
+ if (i < [_colorProfilesArray count]) {
+ _colorProfilesArray[i] = newColorProfile;
+ } else {
+ [_colorProfilesArray addObject:newColorProfile];
+ }
+ }
+ };
+
+ // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+ // For documentation please refer to the DDAbstractLogger implementation.
+
+ if ([self isOnInternalLoggerQueue]) {
+ block();
+ } else {
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+ }
+}
+
+- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forTag:(id <NSCopying>)tag {
+ NSAssert([(id < NSObject >) tag conformsToProtocol: @protocol(NSCopying)], @"Invalid tag");
+
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ DDTTYLoggerColorProfile *newColorProfile =
+ [[DDTTYLoggerColorProfile alloc] initWithForegroundColor:txtColor
+ backgroundColor:bgColor
+ flag:(DDLogFlag)0
+ context:0];
+
+ NSLogInfo(@"DDTTYLogger: newColorProfile: %@", newColorProfile);
+
+ _colorProfilesDict[tag] = newColorProfile;
+ }
+ };
+
+ // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+ // For documentation please refer to the DDAbstractLogger implementation.
+
+ if ([self isOnInternalLoggerQueue]) {
+ block();
+ } else {
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+ }
+}
+
+- (void)clearColorsForFlag:(DDLogFlag)mask {
+ [self clearColorsForFlag:mask context:0];
+}
+
+- (void)clearColorsForFlag:(DDLogFlag)mask context:(NSInteger)context {
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ NSUInteger i = 0;
+
+ for (DDTTYLoggerColorProfile *colorProfile in _colorProfilesArray) {
+ if ((colorProfile->mask == mask) && (colorProfile->context == context)) {
+ break;
+ }
+
+ i++;
+ }
+
+ if (i < [_colorProfilesArray count]) {
+ [_colorProfilesArray removeObjectAtIndex:i];
+ }
+ }
+ };
+
+ // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+ // For documentation please refer to the DDAbstractLogger implementation.
+
+ if ([self isOnInternalLoggerQueue]) {
+ block();
+ } else {
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+ }
+}
+
+- (void)clearColorsForTag:(id <NSCopying>)tag {
+ NSAssert([(id < NSObject >) tag conformsToProtocol: @protocol(NSCopying)], @"Invalid tag");
+
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ [_colorProfilesDict removeObjectForKey:tag];
+ }
+ };
+
+ // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+ // For documentation please refer to the DDAbstractLogger implementation.
+
+ if ([self isOnInternalLoggerQueue]) {
+ block();
+ } else {
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+ }
+}
+
+- (void)clearColorsForAllFlags {
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ [_colorProfilesArray removeAllObjects];
+ }
+ };
+
+ // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+ // For documentation please refer to the DDAbstractLogger implementation.
+
+ if ([self isOnInternalLoggerQueue]) {
+ block();
+ } else {
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+ }
+}
+
+- (void)clearColorsForAllTags {
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ [_colorProfilesDict removeAllObjects];
+ }
+ };
+
+ // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+ // For documentation please refer to the DDAbstractLogger implementation.
+
+ if ([self isOnInternalLoggerQueue]) {
+ block();
+ } else {
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+ }
+}
+
+- (void)clearAllColors {
+ dispatch_block_t block = ^{
+ @autoreleasepool {
+ [_colorProfilesArray removeAllObjects];
+ [_colorProfilesDict removeAllObjects];
+ }
+ };
+
+ // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+ // For documentation please refer to the DDAbstractLogger implementation.
+
+ if ([self isOnInternalLoggerQueue]) {
+ block();
+ } else {
+ dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
+ NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+
+ dispatch_async(globalLoggingQueue, ^{
+ dispatch_async(self.loggerQueue, block);
+ });
+ }
+}
+
+- (void)logMessage:(DDLogMessage *)logMessage {
+ NSString *logMsg = logMessage->_message;
+ BOOL isFormatted = NO;
+
+ if (_logFormatter) {
+ logMsg = [_logFormatter formatLogMessage:logMessage];
+ isFormatted = logMsg != logMessage->_message;
+ }
+
+ if (logMsg) {
+ // Search for a color profile associated with the log message
+
+ DDTTYLoggerColorProfile *colorProfile = nil;
+
+ if (_colorsEnabled) {
+ if (logMessage->_tag) {
+ colorProfile = _colorProfilesDict[logMessage->_tag];
+ }
+
+ if (colorProfile == nil) {
+ for (DDTTYLoggerColorProfile *cp in _colorProfilesArray) {
+ if (logMessage->_flag & cp->mask) {
+ // Color profile set for this context?
+ if (logMessage->_context == cp->context) {
+ colorProfile = cp;
+
+ // Stop searching
+ break;
+ }
+
+ // Check if LOG_CONTEXT_ALL was specified as a default color for this flag
+ if (cp->context == LOG_CONTEXT_ALL) {
+ colorProfile = cp;
+
+ // We don't break to keep searching for more specific color profiles for the context
+ }
+ }
+ }
+ }
+ }
+
+ // Convert log message to C string.
+ //
+ // We use the stack instead of the heap for speed if possible.
+ // But we're extra cautious to avoid a stack overflow.
+
+ NSUInteger msgLen = [logMsg lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+ const BOOL useStack = msgLen < (1024 * 4);
+
+ char msgStack[useStack ? (msgLen + 1) : 1]; // Analyzer doesn't like zero-size array, hence the 1
+ char *msg = useStack ? msgStack : (char *)malloc(msgLen + 1);
+
+ if (msg == NULL) {
+ return;
+ }
+
+ BOOL logMsgEnc = [logMsg getCString:msg maxLength:(msgLen + 1) encoding:NSUTF8StringEncoding];
+
+ if (!logMsgEnc) {
+ if (!useStack && msg != NULL) {
+ free(msg);
+ }
+
+ return;
+ }
+
+ // Write the log message to STDERR
+
+ if (isFormatted) {
+ // The log message has already been formatted.
+ int iovec_len = (_automaticallyAppendNewlineForCustomFormatters) ? 5 : 4;
+ struct iovec v[iovec_len];
+
+ if (colorProfile) {
+ v[0].iov_base = colorProfile->fgCode;
+ v[0].iov_len = colorProfile->fgCodeLen;
+
+ v[1].iov_base = colorProfile->bgCode;
+ v[1].iov_len = colorProfile->bgCodeLen;
+
+ v[iovec_len - 1].iov_base = colorProfile->resetCode;
+ v[iovec_len - 1].iov_len = colorProfile->resetCodeLen;
+ } else {
+ v[0].iov_base = "";
+ v[0].iov_len = 0;
+
+ v[1].iov_base = "";
+ v[1].iov_len = 0;
+
+ v[iovec_len - 1].iov_base = "";
+ v[iovec_len - 1].iov_len = 0;
+ }
+
+ v[2].iov_base = (char *)msg;
+ v[2].iov_len = msgLen;
+
+ if (iovec_len == 5) {
+ v[3].iov_base = "\n";
+ v[3].iov_len = (msg[msgLen] == '\n') ? 0 : 1;
+ }
+
+ writev(STDERR_FILENO, v, iovec_len);
+ } else {
+ // The log message is unformatted, so apply standard NSLog style formatting.
+
+ int len;
+ char ts[24] = "";
+ size_t tsLen = 0;
+
+ // Calculate timestamp.
+ // The technique below is faster than using NSDateFormatter.
+ if (logMessage->_timestamp) {
+ NSDateComponents *components = [[NSCalendar autoupdatingCurrentCalendar] components:_calendarUnitFlags fromDate:logMessage->_timestamp];
+
+ NSTimeInterval epoch = [logMessage->_timestamp timeIntervalSinceReferenceDate];
+ int milliseconds = (int)((epoch - floor(epoch)) * 1000);
+
+ len = snprintf(ts, 24, "%04ld-%02ld-%02ld %02ld:%02ld:%02ld:%03d", // yyyy-MM-dd HH:mm:ss:SSS
+ (long)components.year,
+ (long)components.month,
+ (long)components.day,
+ (long)components.hour,
+ (long)components.minute,
+ (long)components.second, milliseconds);
+
+ tsLen = (NSUInteger)MAX(MIN(24 - 1, len), 0);
+ }
+
+ // Calculate thread ID
+ //
+ // How many characters do we need for the thread id?
+ // logMessage->machThreadID is of type mach_port_t, which is an unsigned int.
+ //
+ // 1 hex char = 4 bits
+ // 8 hex chars for 32 bit, plus ending '\0' = 9
+
+ char tid[9];
+ len = snprintf(tid, 9, "%s", [logMessage->_threadID cStringUsingEncoding:NSUTF8StringEncoding]);
+
+ size_t tidLen = (NSUInteger)MAX(MIN(9 - 1, len), 0);
+
+ // Here is our format: "%s %s[%i:%s] %s", timestamp, appName, processID, threadID, logMsg
+
+ struct iovec v[13];
+
+ if (colorProfile) {
+ v[0].iov_base = colorProfile->fgCode;
+ v[0].iov_len = colorProfile->fgCodeLen;
+
+ v[1].iov_base = colorProfile->bgCode;
+ v[1].iov_len = colorProfile->bgCodeLen;
+
+ v[12].iov_base = colorProfile->resetCode;
+ v[12].iov_len = colorProfile->resetCodeLen;
+ } else {
+ v[0].iov_base = "";
+ v[0].iov_len = 0;
+
+ v[1].iov_base = "";
+ v[1].iov_len = 0;
+
+ v[12].iov_base = "";
+ v[12].iov_len = 0;
+ }
+
+ v[2].iov_base = ts;
+ v[2].iov_len = tsLen;
+
+ v[3].iov_base = " ";
+ v[3].iov_len = 1;
+
+ v[4].iov_base = _app;
+ v[4].iov_len = _appLen;
+
+ v[5].iov_base = "[";
+ v[5].iov_len = 1;
+
+ v[6].iov_base = _pid;
+ v[6].iov_len = _pidLen;
+
+ v[7].iov_base = ":";
+ v[7].iov_len = 1;
+
+ v[8].iov_base = tid;
+ v[8].iov_len = MIN((size_t)8, tidLen); // snprintf doesn't return what you might think
+
+ v[9].iov_base = "] ";
+ v[9].iov_len = 2;
+
+ v[10].iov_base = (char *)msg;
+ v[10].iov_len = msgLen;
+
+ v[11].iov_base = "\n";
+ v[11].iov_len = (msg[msgLen] == '\n') ? 0 : 1;
+
+ writev(STDERR_FILENO, v, 13);
+ }
+
+ if (!useStack) {
+ free(msg);
+ }
+ }
+}
+
+- (NSString *)loggerName {
+ return @"cocoa.lumberjack.ttyLogger";
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation DDTTYLoggerColorProfile
+
+- (instancetype)initWithForegroundColor:(DDColor *)fgColor backgroundColor:(DDColor *)bgColor flag:(DDLogFlag)aMask context:(NSInteger)ctxt {
+ if ((self = [super init])) {
+ mask = aMask;
+ context = ctxt;
+
+ CGFloat r, g, b;
+
+ if (fgColor) {
+ [DDTTYLogger getRed:&r green:&g blue:&b fromColor:fgColor];
+
+ fg_r = (uint8_t)(r * 255.0f);
+ fg_g = (uint8_t)(g * 255.0f);
+ fg_b = (uint8_t)(b * 255.0f);
+ }
+
+ if (bgColor) {
+ [DDTTYLogger getRed:&r green:&g blue:&b fromColor:bgColor];
+
+ bg_r = (uint8_t)(r * 255.0f);
+ bg_g = (uint8_t)(g * 255.0f);
+ bg_b = (uint8_t)(b * 255.0f);
+ }
+
+ if (fgColor && isaColorTTY) {
+ // Map foreground color to closest available shell color
+
+ fgCodeIndex = [DDTTYLogger codeIndexForColor:fgColor];
+ fgCodeRaw = codes_fg[fgCodeIndex];
+
+ NSString *escapeSeq = @"\033[";
+
+ NSUInteger len1 = [escapeSeq lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+ NSUInteger len2 = [fgCodeRaw lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+
+ BOOL escapeSeqEnc = [escapeSeq getCString:(fgCode) maxLength:(len1 + 1) encoding:NSUTF8StringEncoding];
+ BOOL fgCodeRawEsc = [fgCodeRaw getCString:(fgCode + len1) maxLength:(len2 + 1) encoding:NSUTF8StringEncoding];
+
+ if (!escapeSeqEnc || !fgCodeRawEsc) {
+ return nil;
+ }
+
+ fgCodeLen = len1 + len2;
+ } else if (fgColor && isaXcodeColorTTY) {
+ // Convert foreground color to color code sequence
+
+ const char *escapeSeq = XCODE_COLORS_ESCAPE_SEQ;
+
+ int result = snprintf(fgCode, 24, "%sfg%u,%u,%u;", escapeSeq, fg_r, fg_g, fg_b);
+ fgCodeLen = (NSUInteger)MAX(MIN(result, (24 - 1)), 0);
+ } else {
+ // No foreground color or no color support
+
+ fgCode[0] = '\0';
+ fgCodeLen = 0;
+ }
+
+ if (bgColor && isaColorTTY) {
+ // Map background color to closest available shell color
+
+ bgCodeIndex = [DDTTYLogger codeIndexForColor:bgColor];
+ bgCodeRaw = codes_bg[bgCodeIndex];
+
+ NSString *escapeSeq = @"\033[";
+
+ NSUInteger len1 = [escapeSeq lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+ NSUInteger len2 = [bgCodeRaw lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+
+ BOOL escapeSeqEnc = [escapeSeq getCString:(bgCode) maxLength:(len1 + 1) encoding:NSUTF8StringEncoding];
+ BOOL bgCodeRawEsc = [bgCodeRaw getCString:(bgCode + len1) maxLength:(len2 + 1) encoding:NSUTF8StringEncoding];
+
+ if (!escapeSeqEnc || !bgCodeRawEsc) {
+ return nil;
+ }
+
+ bgCodeLen = len1 + len2;
+ } else if (bgColor && isaXcodeColorTTY) {
+ // Convert background color to color code sequence
+
+ const char *escapeSeq = XCODE_COLORS_ESCAPE_SEQ;
+
+ int result = snprintf(bgCode, 24, "%sbg%u,%u,%u;", escapeSeq, bg_r, bg_g, bg_b);
+ bgCodeLen = (NSUInteger)MAX(MIN(result, (24 - 1)), 0);
+ } else {
+ // No background color or no color support
+
+ bgCode[0] = '\0';
+ bgCodeLen = 0;
+ }
+
+ if (isaColorTTY) {
+ resetCodeLen = (NSUInteger)MAX(snprintf(resetCode, 8, "\033[0m"), 0);
+ } else if (isaXcodeColorTTY) {
+ resetCodeLen = (NSUInteger)MAX(snprintf(resetCode, 8, XCODE_COLORS_RESET), 0);
+ } else {
+ resetCode[0] = '\0';
+ resetCodeLen = 0;
+ }
+ }
+
+ return self;
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:
+ @"<DDTTYLoggerColorProfile: %p mask:%i ctxt:%ld fg:%u,%u,%u bg:%u,%u,%u fgCode:%@ bgCode:%@>",
+ self, (int)mask, (long)context, fg_r, fg_g, fg_b, bg_r, bg_g, bg_b, fgCodeRaw, bgCodeRaw];
+}
+
+@end
diff --git a/cocoalumberjack/Classes/Extensions/._DDContextFilterLogFormatter.h b/cocoalumberjack/Classes/Extensions/._DDContextFilterLogFormatter.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/Extensions/._DDContextFilterLogFormatter.h
Binary files differ
diff --git a/cocoalumberjack/Classes/Extensions/._DDContextFilterLogFormatter.m b/cocoalumberjack/Classes/Extensions/._DDContextFilterLogFormatter.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/Extensions/._DDContextFilterLogFormatter.m
Binary files differ
diff --git a/cocoalumberjack/Classes/Extensions/._DDDispatchQueueLogFormatter.h b/cocoalumberjack/Classes/Extensions/._DDDispatchQueueLogFormatter.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/Extensions/._DDDispatchQueueLogFormatter.h
Binary files differ
diff --git a/cocoalumberjack/Classes/Extensions/._DDDispatchQueueLogFormatter.m b/cocoalumberjack/Classes/Extensions/._DDDispatchQueueLogFormatter.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/Extensions/._DDDispatchQueueLogFormatter.m
Binary files differ
diff --git a/cocoalumberjack/Classes/Extensions/._DDMultiFormatter.h b/cocoalumberjack/Classes/Extensions/._DDMultiFormatter.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/Extensions/._DDMultiFormatter.h
Binary files differ
diff --git a/cocoalumberjack/Classes/Extensions/._DDMultiFormatter.m b/cocoalumberjack/Classes/Extensions/._DDMultiFormatter.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Classes/Extensions/._DDMultiFormatter.m
Binary files differ
diff --git a/cocoalumberjack/Classes/Extensions/DDContextFilterLogFormatter.h b/cocoalumberjack/Classes/Extensions/DDContextFilterLogFormatter.h
new file mode 100644
index 0000000..1657f1f
--- /dev/null
+++ b/cocoalumberjack/Classes/Extensions/DDContextFilterLogFormatter.h
@@ -0,0 +1,117 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import <Foundation/Foundation.h>
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+ #define DD_LEGACY_MACROS 0
+#endif
+
+#import "DDLog.h"
+
+/**
+ * This class provides a log formatter that filters log statements from a logging context not on the whitelist.
+ *
+ * A log formatter can be added to any logger to format and/or filter its output.
+ * You can learn more about log formatters here:
+ * Documentation/CustomFormatters.md
+ *
+ * You can learn more about logging context's here:
+ * Documentation/CustomContext.md
+ *
+ * But here's a quick overview / refresher:
+ *
+ * Every log statement has a logging context.
+ * These come from the underlying logging macros defined in DDLog.h.
+ * The default logging context is zero.
+ * You can define multiple logging context's for use in your application.
+ * For example, logically separate parts of your app each have a different logging context.
+ * Also 3rd party frameworks that make use of Lumberjack generally use their own dedicated logging context.
+ **/
+@interface DDContextWhitelistFilterLogFormatter : NSObject <DDLogFormatter>
+
+/**
+ * Designated default initializer
+ */
+- (instancetype)init NS_DESIGNATED_INITIALIZER;
+
+/**
+ * Add a context to the whitelist
+ *
+ * @param loggingContext the context
+ */
+- (void)addToWhitelist:(NSUInteger)loggingContext;
+
+/**
+ * Remove context from whitelist
+ *
+ * @param loggingContext the context
+ */
+- (void)removeFromWhitelist:(NSUInteger)loggingContext;
+
+/**
+ * Return the whitelist
+ */
+@property (readonly, copy) NSArray *whitelist;
+
+/**
+ * Check if a context is on the whitelist
+ *
+ * @param loggingContext the context
+ */
+- (BOOL)isOnWhitelist:(NSUInteger)loggingContext;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This class provides a log formatter that filters log statements from a logging context on the blacklist.
+ **/
+@interface DDContextBlacklistFilterLogFormatter : NSObject <DDLogFormatter>
+
+- (instancetype)init NS_DESIGNATED_INITIALIZER;
+
+/**
+ * Add a context to the blacklist
+ *
+ * @param loggingContext the context
+ */
+- (void)addToBlacklist:(NSUInteger)loggingContext;
+
+/**
+ * Remove context from blacklist
+ *
+ * @param loggingContext the context
+ */
+- (void)removeFromBlacklist:(NSUInteger)loggingContext;
+
+/**
+ * Return the blacklist
+ */
+@property (readonly, copy) NSArray *blacklist;
+
+
+/**
+ * Check if a context is on the blacklist
+ *
+ * @param loggingContext the context
+ */
+- (BOOL)isOnBlacklist:(NSUInteger)loggingContext;
+
+@end
diff --git a/cocoalumberjack/Classes/Extensions/DDContextFilterLogFormatter.m b/cocoalumberjack/Classes/Extensions/DDContextFilterLogFormatter.m
new file mode 100644
index 0000000..b6d6c8a
--- /dev/null
+++ b/cocoalumberjack/Classes/Extensions/DDContextFilterLogFormatter.m
@@ -0,0 +1,192 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import "DDContextFilterLogFormatter.h"
+#import <libkern/OSAtomic.h>
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+@interface DDLoggingContextSet : NSObject
+
+- (void)addToSet:(NSUInteger)loggingContext;
+- (void)removeFromSet:(NSUInteger)loggingContext;
+
+@property (readonly, copy) NSArray *currentSet;
+
+- (BOOL)isInSet:(NSUInteger)loggingContext;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface DDContextWhitelistFilterLogFormatter () {
+ DDLoggingContextSet *_contextSet;
+}
+
+@end
+
+
+@implementation DDContextWhitelistFilterLogFormatter
+
+- (instancetype)init {
+ if ((self = [super init])) {
+ _contextSet = [[DDLoggingContextSet alloc] init];
+ }
+
+ return self;
+}
+
+- (void)addToWhitelist:(NSUInteger)loggingContext {
+ [_contextSet addToSet:loggingContext];
+}
+
+- (void)removeFromWhitelist:(NSUInteger)loggingContext {
+ [_contextSet removeFromSet:loggingContext];
+}
+
+- (NSArray *)whitelist {
+ return [_contextSet currentSet];
+}
+
+- (BOOL)isOnWhitelist:(NSUInteger)loggingContext {
+ return [_contextSet isInSet:loggingContext];
+}
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+ if ([self isOnWhitelist:logMessage->_context]) {
+ return logMessage->_message;
+ } else {
+ return nil;
+ }
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface DDContextBlacklistFilterLogFormatter () {
+ DDLoggingContextSet *_contextSet;
+}
+
+@end
+
+
+@implementation DDContextBlacklistFilterLogFormatter
+
+- (instancetype)init {
+ if ((self = [super init])) {
+ _contextSet = [[DDLoggingContextSet alloc] init];
+ }
+
+ return self;
+}
+
+- (void)addToBlacklist:(NSUInteger)loggingContext {
+ [_contextSet addToSet:loggingContext];
+}
+
+- (void)removeFromBlacklist:(NSUInteger)loggingContext {
+ [_contextSet removeFromSet:loggingContext];
+}
+
+- (NSArray *)blacklist {
+ return [_contextSet currentSet];
+}
+
+- (BOOL)isOnBlacklist:(NSUInteger)loggingContext {
+ return [_contextSet isInSet:loggingContext];
+}
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+ if ([self isOnBlacklist:logMessage->_context]) {
+ return nil;
+ } else {
+ return logMessage->_message;
+ }
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+@interface DDLoggingContextSet () {
+ OSSpinLock _lock;
+ NSMutableSet *_set;
+}
+
+@end
+
+
+@implementation DDLoggingContextSet
+
+- (instancetype)init {
+ if ((self = [super init])) {
+ _set = [[NSMutableSet alloc] init];
+ _lock = OS_SPINLOCK_INIT;
+ }
+
+ return self;
+}
+
+- (void)addToSet:(NSUInteger)loggingContext {
+ OSSpinLockLock(&_lock);
+ {
+ [_set addObject:@(loggingContext)];
+ }
+ OSSpinLockUnlock(&_lock);
+}
+
+- (void)removeFromSet:(NSUInteger)loggingContext {
+ OSSpinLockLock(&_lock);
+ {
+ [_set removeObject:@(loggingContext)];
+ }
+ OSSpinLockUnlock(&_lock);
+}
+
+- (NSArray *)currentSet {
+ NSArray *result = nil;
+
+ OSSpinLockLock(&_lock);
+ {
+ result = [_set allObjects];
+ }
+ OSSpinLockUnlock(&_lock);
+
+ return result;
+}
+
+- (BOOL)isInSet:(NSUInteger)loggingContext {
+ BOOL result = NO;
+
+ OSSpinLockLock(&_lock);
+ {
+ result = [_set containsObject:@(loggingContext)];
+ }
+ OSSpinLockUnlock(&_lock);
+
+ return result;
+}
+
+@end
diff --git a/cocoalumberjack/Classes/Extensions/DDDispatchQueueLogFormatter.h b/cocoalumberjack/Classes/Extensions/DDDispatchQueueLogFormatter.h
new file mode 100644
index 0000000..129f6e1
--- /dev/null
+++ b/cocoalumberjack/Classes/Extensions/DDDispatchQueueLogFormatter.h
@@ -0,0 +1,178 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import <Foundation/Foundation.h>
+#import <libkern/OSAtomic.h>
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+ #define DD_LEGACY_MACROS 0
+#endif
+
+#import "DDLog.h"
+
+/**
+ * Log formatter mode
+ */
+typedef NS_ENUM(NSUInteger, DDDispatchQueueLogFormatterMode){
+ /**
+ * This is the default option, means the formatter can be reused between multiple loggers and therefore is thread-safe.
+ * There is, of course, a performance cost for the thread-safety
+ */
+ DDDispatchQueueLogFormatterModeShareble = 0,
+ /**
+ * If the formatter will only be used by a single logger, then the thread-safety can be removed
+ * @note: there is an assert checking if the formatter is added to multiple loggers and the mode is non-shareble
+ */
+ DDDispatchQueueLogFormatterModeNonShareble,
+};
+
+
+/**
+ * This class provides a log formatter that prints the dispatch_queue label instead of the mach_thread_id.
+ *
+ * A log formatter can be added to any logger to format and/or filter its output.
+ * You can learn more about log formatters here:
+ * Documentation/CustomFormatters.md
+ *
+ * A typical `NSLog` (or `DDTTYLogger`) prints detailed info as `[<process_id>:<thread_id>]`.
+ * For example:
+ *
+ * `2011-10-17 20:21:45.435 AppName[19928:5207] Your log message here`
+ *
+ * Where:
+ * `- 19928 = process id`
+ * `- 5207 = thread id (mach_thread_id printed in hex)`
+ *
+ * When using grand central dispatch (GCD), this information is less useful.
+ * This is because a single serial dispatch queue may be run on any thread from an internally managed thread pool.
+ * For example:
+ *
+ * `2011-10-17 20:32:31.111 AppName[19954:4d07] Message from my_serial_dispatch_queue`
+ * `2011-10-17 20:32:31.112 AppName[19954:5207] Message from my_serial_dispatch_queue`
+ * `2011-10-17 20:32:31.113 AppName[19954:2c55] Message from my_serial_dispatch_queue`
+ *
+ * This formatter allows you to replace the standard `[box:info]` with the dispatch_queue name.
+ * For example:
+ *
+ * `2011-10-17 20:32:31.111 AppName[img-scaling] Message from my_serial_dispatch_queue`
+ * `2011-10-17 20:32:31.112 AppName[img-scaling] Message from my_serial_dispatch_queue`
+ * `2011-10-17 20:32:31.113 AppName[img-scaling] Message from my_serial_dispatch_queue`
+ *
+ * If the dispatch_queue doesn't have a set name, then it falls back to the thread name.
+ * If the current thread doesn't have a set name, then it falls back to the mach_thread_id in hex (like normal).
+ *
+ * Note: If manually creating your own background threads (via `NSThread/alloc/init` or `NSThread/detachNeThread`),
+ * you can use `[[NSThread currentThread] setName:(NSString *)]`.
+ **/
+@interface DDDispatchQueueLogFormatter : NSObject <DDLogFormatter>
+
+/**
+ * Standard init method.
+ * Configure using properties as desired.
+ **/
+- (instancetype)init NS_DESIGNATED_INITIALIZER;
+
+/**
+ * Initializer with ability to set the queue mode
+ *
+ * @param mode choose between DDDispatchQueueLogFormatterModeShareble and DDDispatchQueueLogFormatterModeNonShareble, depending if the formatter is shared between several loggers or not
+ */
+- (instancetype)initWithMode:(DDDispatchQueueLogFormatterMode)mode;
+
+/**
+ * The minQueueLength restricts the minimum size of the [detail box].
+ * If the minQueueLength is set to 0, there is no restriction.
+ *
+ * For example, say a dispatch_queue has a label of "diskIO":
+ *
+ * If the minQueueLength is 0: [diskIO]
+ * If the minQueueLength is 4: [diskIO]
+ * If the minQueueLength is 5: [diskIO]
+ * If the minQueueLength is 6: [diskIO]
+ * If the minQueueLength is 7: [diskIO ]
+ * If the minQueueLength is 8: [diskIO ]
+ *
+ * The default minQueueLength is 0 (no minimum, so [detail box] won't be padded).
+ *
+ * If you want every [detail box] to have the exact same width,
+ * set both minQueueLength and maxQueueLength to the same value.
+ **/
+@property (assign, atomic) NSUInteger minQueueLength;
+
+/**
+ * The maxQueueLength restricts the number of characters that will be inside the [detail box].
+ * If the maxQueueLength is 0, there is no restriction.
+ *
+ * For example, say a dispatch_queue has a label of "diskIO":
+ *
+ * If the maxQueueLength is 0: [diskIO]
+ * If the maxQueueLength is 4: [disk]
+ * If the maxQueueLength is 5: [diskI]
+ * If the maxQueueLength is 6: [diskIO]
+ * If the maxQueueLength is 7: [diskIO]
+ * If the maxQueueLength is 8: [diskIO]
+ *
+ * The default maxQueueLength is 0 (no maximum, so [detail box] won't be truncated).
+ *
+ * If you want every [detail box] to have the exact same width,
+ * set both minQueueLength and maxQueueLength to the same value.
+ **/
+@property (assign, atomic) NSUInteger maxQueueLength;
+
+/**
+ * Sometimes queue labels have long names like "com.apple.main-queue",
+ * but you'd prefer something shorter like simply "main".
+ *
+ * This method allows you to set such preferred replacements.
+ * The above example is set by default.
+ *
+ * To remove/undo a previous replacement, invoke this method with nil for the 'shortLabel' parameter.
+ **/
+- (NSString *)replacementStringForQueueLabel:(NSString *)longLabel;
+
+/**
+ * See the `replacementStringForQueueLabel:` description
+ */
+- (void)setReplacementString:(NSString *)shortLabel forQueueLabel:(NSString *)longLabel;
+
+@end
+
+/**
+ * Category on `DDDispatchQueueLogFormatter` to make method declarations easier to extend/modify
+ **/
+@interface DDDispatchQueueLogFormatter (OverridableMethods)
+
+/**
+ * Date formatter default configuration
+ */
+- (void)configureDateFormatter:(NSDateFormatter *)dateFormatter;
+
+/**
+ * Formatter method to transfrom from date to string
+ */
+- (NSString *)stringFromDate:(NSDate *)date;
+
+/**
+ * Method to compute the queue thread label
+ */
+- (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage;
+
+/**
+ * The actual method that formats a message (transforms a `DDLogMessage` model into a printable string)
+ */
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage;
+
+@end
diff --git a/cocoalumberjack/Classes/Extensions/DDDispatchQueueLogFormatter.m b/cocoalumberjack/Classes/Extensions/DDDispatchQueueLogFormatter.m
new file mode 100644
index 0000000..c0c3b2e
--- /dev/null
+++ b/cocoalumberjack/Classes/Extensions/DDDispatchQueueLogFormatter.m
@@ -0,0 +1,278 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import "DDDispatchQueueLogFormatter.h"
+#import <libkern/OSAtomic.h>
+#import <objc/runtime.h>
+
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+@interface DDDispatchQueueLogFormatter () {
+ DDDispatchQueueLogFormatterMode _mode;
+ NSString *_dateFormatterKey;
+
+ int32_t _atomicLoggerCount;
+ NSDateFormatter *_threadUnsafeDateFormatter; // Use [self stringFromDate]
+
+ OSSpinLock _lock;
+
+ NSUInteger _minQueueLength; // _prefix == Only access via atomic property
+ NSUInteger _maxQueueLength; // _prefix == Only access via atomic property
+ NSMutableDictionary *_replacements; // _prefix == Only access from within spinlock
+}
+
+@end
+
+
+@implementation DDDispatchQueueLogFormatter
+
+- (instancetype)init {
+ if ((self = [super init])) {
+ _mode = DDDispatchQueueLogFormatterModeShareble;
+
+ // We need to carefully pick the name for storing in thread dictionary to not
+ // use a formatter configured by subclass and avoid surprises.
+ Class cls = [self class];
+ Class superClass = class_getSuperclass(cls);
+ SEL configMethodName = @selector(configureDateFormatter:);
+ Method configMethod = class_getInstanceMethod(cls, configMethodName);
+ while (class_getInstanceMethod(superClass, configMethodName) == configMethod) {
+ cls = superClass;
+ superClass = class_getSuperclass(cls);
+ }
+ // now `cls` is the class that provides implementation for `configureDateFormatter:`
+ _dateFormatterKey = [NSString stringWithFormat:@"%s_NSDateFormatter", class_getName(cls)];
+
+ _atomicLoggerCount = 0;
+ _threadUnsafeDateFormatter = nil;
+
+ _minQueueLength = 0;
+ _maxQueueLength = 0;
+ _lock = OS_SPINLOCK_INIT;
+ _replacements = [[NSMutableDictionary alloc] init];
+
+ // Set default replacements:
+
+ _replacements[@"com.apple.main-thread"] = @"main";
+ }
+
+ return self;
+}
+
+- (instancetype)initWithMode:(DDDispatchQueueLogFormatterMode)mode {
+ if ((self = [self init])) {
+ _mode = mode;
+ }
+ return self;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@synthesize minQueueLength = _minQueueLength;
+@synthesize maxQueueLength = _maxQueueLength;
+
+- (NSString *)replacementStringForQueueLabel:(NSString *)longLabel {
+ NSString *result = nil;
+
+ OSSpinLockLock(&_lock);
+ {
+ result = _replacements[longLabel];
+ }
+ OSSpinLockUnlock(&_lock);
+
+ return result;
+}
+
+- (void)setReplacementString:(NSString *)shortLabel forQueueLabel:(NSString *)longLabel {
+ OSSpinLockLock(&_lock);
+ {
+ if (shortLabel) {
+ _replacements[longLabel] = shortLabel;
+ } else {
+ [_replacements removeObjectForKey:longLabel];
+ }
+ }
+ OSSpinLockUnlock(&_lock);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark DDLogFormatter
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSDateFormatter *)createDateFormatter {
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ [self configureDateFormatter:formatter];
+ return formatter;
+}
+
+- (void)configureDateFormatter:(NSDateFormatter *)dateFormatter {
+ [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
+ [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss:SSS"];
+ [dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
+
+ NSString *calendarIdentifier = nil;
+#if defined(__IPHONE_8_0) || defined(__MAC_10_10)
+ calendarIdentifier = NSCalendarIdentifierGregorian;
+#else
+ calendarIdentifier = NSGregorianCalendar;
+#endif
+
+ [dateFormatter setCalendar:[[NSCalendar alloc] initWithCalendarIdentifier:calendarIdentifier]];
+}
+
+- (NSString *)stringFromDate:(NSDate *)date {
+
+ NSDateFormatter *dateFormatter = nil;
+ if (_mode == DDDispatchQueueLogFormatterModeNonShareble) {
+ // Single-threaded mode.
+
+ dateFormatter = _threadUnsafeDateFormatter;
+ if (dateFormatter == nil) {
+ dateFormatter = [self createDateFormatter];
+ _threadUnsafeDateFormatter = dateFormatter;
+ }
+ } else {
+ // Multi-threaded mode.
+ // NSDateFormatter is NOT thread-safe.
+
+ NSString *key = _dateFormatterKey;
+
+ NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
+ dateFormatter = threadDictionary[key];
+
+ if (dateFormatter == nil) {
+ dateFormatter = [self createDateFormatter];
+ threadDictionary[key] = dateFormatter;
+ }
+ }
+
+ return [dateFormatter stringFromDate:date];
+}
+
+- (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage {
+ // As per the DDLogFormatter contract, this method is always invoked on the same thread/dispatch_queue
+
+ NSUInteger minQueueLength = self.minQueueLength;
+ NSUInteger maxQueueLength = self.maxQueueLength;
+
+ // Get the name of the queue, thread, or machID (whichever we are to use).
+
+ NSString *queueThreadLabel = nil;
+
+ BOOL useQueueLabel = YES;
+ BOOL useThreadName = NO;
+
+ if (logMessage->_queueLabel) {
+ // If you manually create a thread, it's dispatch_queue will have one of the thread names below.
+ // Since all such threads have the same name, we'd prefer to use the threadName or the machThreadID.
+
+ NSArray *names = @[
+ @"com.apple.root.low-priority",
+ @"com.apple.root.default-priority",
+ @"com.apple.root.high-priority",
+ @"com.apple.root.low-overcommit-priority",
+ @"com.apple.root.default-overcommit-priority",
+ @"com.apple.root.high-overcommit-priority"
+ ];
+
+ for (NSString * name in names) {
+ if ([logMessage->_queueLabel isEqualToString:name]) {
+ useQueueLabel = NO;
+ useThreadName = [logMessage->_threadName length] > 0;
+ break;
+ }
+ }
+ } else {
+ useQueueLabel = NO;
+ useThreadName = [logMessage->_threadName length] > 0;
+ }
+
+ if (useQueueLabel || useThreadName) {
+ NSString *fullLabel;
+ NSString *abrvLabel;
+
+ if (useQueueLabel) {
+ fullLabel = logMessage->_queueLabel;
+ } else {
+ fullLabel = logMessage->_threadName;
+ }
+
+ OSSpinLockLock(&_lock);
+ {
+ abrvLabel = _replacements[fullLabel];
+ }
+ OSSpinLockUnlock(&_lock);
+
+ if (abrvLabel) {
+ queueThreadLabel = abrvLabel;
+ } else {
+ queueThreadLabel = fullLabel;
+ }
+ } else {
+ queueThreadLabel = logMessage->_threadID;
+ }
+
+ // Now use the thread label in the output
+
+ NSUInteger labelLength = [queueThreadLabel length];
+
+ // labelLength > maxQueueLength : truncate
+ // labelLength < minQueueLength : padding
+ // : exact
+
+ if ((maxQueueLength > 0) && (labelLength > maxQueueLength)) {
+ // Truncate
+
+ return [queueThreadLabel substringToIndex:maxQueueLength];
+ } else if (labelLength < minQueueLength) {
+ // Padding
+
+ NSUInteger numSpaces = minQueueLength - labelLength;
+
+ char spaces[numSpaces + 1];
+ memset(spaces, ' ', numSpaces);
+ spaces[numSpaces] = '\0';
+
+ return [NSString stringWithFormat:@"%@%s", queueThreadLabel, spaces];
+ } else {
+ // Exact
+
+ return queueThreadLabel;
+ }
+}
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+ NSString *timestamp = [self stringFromDate:(logMessage->_timestamp)];
+ NSString *queueThreadLabel = [self queueThreadLabelForLogMessage:logMessage];
+
+ return [NSString stringWithFormat:@"%@ [%@] %@", timestamp, queueThreadLabel, logMessage->_message];
+}
+
+- (void)didAddToLogger:(id <DDLogger> __attribute__((unused)))logger {
+ int32_t count = 0;
+ count = OSAtomicIncrement32(&_atomicLoggerCount);
+ NSAssert(count <= 1 || _mode == DDDispatchQueueLogFormatterModeShareble, @"Can't reuse formatter with multiple loggers in non-shareable mode.");
+}
+
+- (void)willRemoveFromLogger:(id <DDLogger> __attribute__((unused)))logger {
+ OSAtomicDecrement32(&_atomicLoggerCount);
+}
+
+@end
diff --git a/cocoalumberjack/Classes/Extensions/DDMultiFormatter.h b/cocoalumberjack/Classes/Extensions/DDMultiFormatter.h
new file mode 100644
index 0000000..1d6ceea
--- /dev/null
+++ b/cocoalumberjack/Classes/Extensions/DDMultiFormatter.h
@@ -0,0 +1,56 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import <Foundation/Foundation.h>
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+ #define DD_LEGACY_MACROS 0
+#endif
+
+#import "DDLog.h"
+
+/**
+ * This formatter can be used to chain different formatters together.
+ * The log message will processed in the order of the formatters added.
+ **/
+@interface DDMultiFormatter : NSObject <DDLogFormatter>
+
+/**
+ * Array of chained formatters
+ */
+@property (readonly) NSArray *formatters;
+
+/**
+ * Add a new formatter
+ */
+- (void)addFormatter:(id<DDLogFormatter>)formatter;
+
+/**
+ * Remove a formatter
+ */
+- (void)removeFormatter:(id<DDLogFormatter>)formatter;
+
+/**
+ * Remove all existing formatters
+ */
+- (void)removeAllFormatters;
+
+/**
+ * Check if a certain formatter is used
+ */
+- (BOOL)isFormattingWithFormatter:(id<DDLogFormatter>)formatter;
+
+@end
diff --git a/cocoalumberjack/Classes/Extensions/DDMultiFormatter.m b/cocoalumberjack/Classes/Extensions/DDMultiFormatter.m
new file mode 100644
index 0000000..c647da3
--- /dev/null
+++ b/cocoalumberjack/Classes/Extensions/DDMultiFormatter.m
@@ -0,0 +1,144 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2015, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific
+// prior written permission of Deusty, LLC.
+
+#import "DDMultiFormatter.h"
+
+
+#if TARGET_OS_IOS
+// Compiling for iOS
+#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later
+#define NEEDS_DISPATCH_RETAIN_RELEASE 0
+#else // iOS 5.X or earlier
+#define NEEDS_DISPATCH_RETAIN_RELEASE 1
+#endif
+#elif TARGET_OS_WATCH || TARGET_OS_TV
+// Compiling for watchOS, tvOS
+#define NEEDS_DISPATCH_RETAIN_RELEASE 0
+#else
+// Compiling for Mac OS X
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later
+#define NEEDS_DISPATCH_RETAIN_RELEASE 0
+#else // Mac OS X 10.7 or earlier
+#define NEEDS_DISPATCH_RETAIN_RELEASE 1
+#endif
+#endif
+
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+
+@interface DDMultiFormatter () {
+ dispatch_queue_t _queue;
+ NSMutableArray *_formatters;
+}
+
+- (DDLogMessage *)logMessageForLine:(NSString *)line originalMessage:(DDLogMessage *)message;
+
+@end
+
+
+@implementation DDMultiFormatter
+
+- (instancetype)init {
+ self = [super init];
+
+ if (self) {
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
+ _queue = dispatch_queue_create("cocoa.lumberjack.multiformatter", DISPATCH_QUEUE_CONCURRENT);
+#else
+ _queue = dispatch_queue_create("cocoa.lumberjack.multiformatter", NULL);
+#endif
+ _formatters = [NSMutableArray new];
+ }
+
+ return self;
+}
+
+#if NEEDS_DISPATCH_RETAIN_RELEASE
+- (void)dealloc {
+ dispatch_release(_queue);
+}
+
+#endif
+
+#pragma mark Processing
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+ __block NSString *line = logMessage->_message;
+
+ dispatch_sync(_queue, ^{
+ for (id<DDLogFormatter> formatter in _formatters) {
+ DDLogMessage *message = [self logMessageForLine:line originalMessage:logMessage];
+ line = [formatter formatLogMessage:message];
+
+ if (!line) {
+ break;
+ }
+ }
+ });
+
+ return line;
+}
+
+- (DDLogMessage *)logMessageForLine:(NSString *)line originalMessage:(DDLogMessage *)message {
+ DDLogMessage *newMessage = [message copy];
+
+ newMessage->_message = line;
+ return newMessage;
+}
+
+#pragma mark Formatters
+
+- (NSArray *)formatters {
+ __block NSArray *formatters;
+
+ dispatch_sync(_queue, ^{
+ formatters = [_formatters copy];
+ });
+
+ return formatters;
+}
+
+- (void)addFormatter:(id<DDLogFormatter>)formatter {
+ dispatch_barrier_async(_queue, ^{
+ [_formatters addObject:formatter];
+ });
+}
+
+- (void)removeFormatter:(id<DDLogFormatter>)formatter {
+ dispatch_barrier_async(_queue, ^{
+ [_formatters removeObject:formatter];
+ });
+}
+
+- (void)removeAllFormatters {
+ dispatch_barrier_async(_queue, ^{
+ [_formatters removeAllObjects];
+ });
+}
+
+- (BOOL)isFormattingWithFormatter:(id<DDLogFormatter>)formatter {
+ __block BOOL hasFormatter;
+
+ dispatch_sync(_queue, ^{
+ hasFormatter = [_formatters containsObject:formatter];
+ });
+
+ return hasFormatter;
+}
+
+@end
diff --git a/cocoalumberjack/CocoaLumberjack.podspec b/cocoalumberjack/CocoaLumberjack.podspec
new file mode 100644
index 0000000..c80a8ef
--- /dev/null
+++ b/cocoalumberjack/CocoaLumberjack.podspec
@@ -0,0 +1,60 @@
+
+Pod::Spec.new do |s|
+
+ s.name = 'CocoaLumberjack'
+ s.version = '2.2.1nest'
+ s.license = 'BSD'
+ s.summary = 'A fast & simple, yet powerful & flexible logging framework for Mac and iOS.'
+ s.homepage = 'https://github.com/CocoaLumberjack/CocoaLumberjack'
+ s.author = { 'Robbie Hanson' => 'robbiehanson@deusty.com' }
+ s.source = { :git => 'https://github.com/CocoaLumberjack/CocoaLumberjack.git',
+ :tag => "#{s.version}" }
+
+ s.description = 'It is similar in concept to other popular logging frameworks such as log4j, ' \
+ 'yet is designed specifically for objective-c, and takes advantage of features ' \
+ 'such as multi-threading, grand central dispatch (if available), lockless ' \
+ 'atomic operations, and the dynamic nature of the objective-c runtime.'
+
+ s.requires_arc = true
+
+ s.preserve_paths = 'README.md', 'Classes/CocoaLumberjack.swift', 'Framework/Lumberjack/CocoaLumberjack.modulemap'
+ s.ios.deployment_target = '5.0'
+ s.osx.deployment_target = '10.7'
+ s.watchos.deployment_target = '2.0'
+ s.tvos.deployment_target = '9.0'
+
+ s.public_header_files = 'Classes/*.h'
+
+ s.module_map = 'Framework/Lumberjack/CocoaLumberjack.modulemap'
+ s.default_subspecs = 'Default', 'Extensions'
+
+ s.subspec 'Default' do |ss|
+ ss.source_files = 'Classes/CocoaLumberjack.{h,m}'
+ ss.dependency 'CocoaLumberjack/Core'
+ end
+
+ s.subspec 'Core' do |ss|
+ ss.source_files = 'Classes/DD*.{h,m}'
+ end
+
+ s.subspec 'Extensions' do |ss|
+ ss.source_files = 'Classes/Extensions/*.{h,m}'
+ ss.dependency 'CocoaLumberjack/Default'
+ end
+
+ s.subspec 'CLI' do |ss|
+ ss.osx.deployment_target = '10.7'
+ ss.source_files = 'Classes/CLI/*.{h,m}'
+ ss.dependency 'CocoaLumberjack/Default'
+ end
+
+ s.subspec 'Swift' do |ss|
+ ss.ios.deployment_target = '8.0'
+ ss.osx.deployment_target = '10.10'
+ ss.watchos.deployment_target = '2.0'
+ ss.tvos.deployment_target = '9.0'
+ ss.source_files = 'Classes/CocoaLumberjack.swift'
+ ss.dependency 'CocoaLumberjack/Extensions'
+ end
+
+end
diff --git a/cocoalumberjack/Demos/._Benchmark b/cocoalumberjack/Demos/._Benchmark
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._Benchmark
Binary files differ
diff --git a/cocoalumberjack/Demos/._CLI b/cocoalumberjack/Demos/._CLI
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._CLI
Binary files differ
diff --git a/cocoalumberjack/Demos/._CaptureASL b/cocoalumberjack/Demos/._CaptureASL
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._CaptureASL
Binary files differ
diff --git a/cocoalumberjack/Demos/._ContextFilter b/cocoalumberjack/Demos/._ContextFilter
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._ContextFilter
Binary files differ
diff --git a/cocoalumberjack/Demos/._CoreDataLogger b/cocoalumberjack/Demos/._CoreDataLogger
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._CoreDataLogger
Binary files differ
diff --git a/cocoalumberjack/Demos/._CustomFormatters b/cocoalumberjack/Demos/._CustomFormatters
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._CustomFormatters
Binary files differ
diff --git a/cocoalumberjack/Demos/._CustomLogLevels b/cocoalumberjack/Demos/._CustomLogLevels
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._CustomLogLevels
Binary files differ
diff --git a/cocoalumberjack/Demos/._Demos.xcworkspace b/cocoalumberjack/Demos/._Demos.xcworkspace
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._Demos.xcworkspace
Binary files differ
diff --git a/cocoalumberjack/Demos/._DispatchQueueLogger b/cocoalumberjack/Demos/._DispatchQueueLogger
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._DispatchQueueLogger
Binary files differ
diff --git a/cocoalumberjack/Demos/._FineGrainedLogging b/cocoalumberjack/Demos/._FineGrainedLogging
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._FineGrainedLogging
Binary files differ
diff --git a/cocoalumberjack/Demos/._GlobalLogLevel b/cocoalumberjack/Demos/._GlobalLogLevel
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._GlobalLogLevel
Binary files differ
diff --git a/cocoalumberjack/Demos/._LogFileCompressor b/cocoalumberjack/Demos/._LogFileCompressor
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._LogFileCompressor
Binary files differ
diff --git a/cocoalumberjack/Demos/._NonArcTest b/cocoalumberjack/Demos/._NonArcTest
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._NonArcTest
Binary files differ
diff --git a/cocoalumberjack/Demos/._OverflowTestMac b/cocoalumberjack/Demos/._OverflowTestMac
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._OverflowTestMac
Binary files differ
diff --git a/cocoalumberjack/Demos/._PerUserLogLevels b/cocoalumberjack/Demos/._PerUserLogLevels
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._PerUserLogLevels
Binary files differ
diff --git a/cocoalumberjack/Demos/._RegisteredDynamicLogging b/cocoalumberjack/Demos/._RegisteredDynamicLogging
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._RegisteredDynamicLogging
Binary files differ
diff --git a/cocoalumberjack/Demos/._RollingTestMac b/cocoalumberjack/Demos/._RollingTestMac
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._RollingTestMac
Binary files differ
diff --git a/cocoalumberjack/Demos/._SQLiteLogger b/cocoalumberjack/Demos/._SQLiteLogger
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._SQLiteLogger
Binary files differ
diff --git a/cocoalumberjack/Demos/._TestXcodeColors b/cocoalumberjack/Demos/._TestXcodeColors
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._TestXcodeColors
Binary files differ
diff --git a/cocoalumberjack/Demos/._UniversalApp b/cocoalumberjack/Demos/._UniversalApp
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._UniversalApp
Binary files differ
diff --git a/cocoalumberjack/Demos/._WebServerIPhone b/cocoalumberjack/Demos/._WebServerIPhone
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/._WebServerIPhone
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/._Desktop b/cocoalumberjack/Demos/Benchmark/._Desktop
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/._Desktop
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/._Mobile b/cocoalumberjack/Demos/Benchmark/._Mobile
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/._Mobile
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/._BenchmarkMac b/cocoalumberjack/Demos/Benchmark/Desktop/._BenchmarkMac
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/._BenchmarkMac
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/._BenchmarkMac.xcodeproj b/cocoalumberjack/Demos/Benchmark/Desktop/._BenchmarkMac.xcodeproj
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/._BenchmarkMac.xcodeproj
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/._Podfile b/cocoalumberjack/Demos/Benchmark/Desktop/._Podfile
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/._Podfile
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/._ReadMe.txt b/cocoalumberjack/Demos/Benchmark/Desktop/._ReadMe.txt
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/._ReadMe.txt
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/._project.pbxproj b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/._project.pbxproj
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/._project.pbxproj
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/._xcshareddata b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/._xcshareddata
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/._xcshareddata
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/project.pbxproj b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..62b8bf9
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/project.pbxproj
@@ -0,0 +1,404 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ DCC29DE114730AA9005E8FD4 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC29DE014730AA9005E8FD4 /* Cocoa.framework */; };
+ DCC29DEB14730AA9005E8FD4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DCC29DE914730AA9005E8FD4 /* InfoPlist.strings */; };
+ DCC29DED14730AA9005E8FD4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC29DEC14730AA9005E8FD4 /* main.m */; };
+ DCC29DF114730AA9005E8FD4 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = DCC29DEF14730AA9005E8FD4 /* Credits.rtf */; };
+ DCC29DF414730AA9005E8FD4 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC29DF314730AA9005E8FD4 /* AppDelegate.m */; };
+ DCC29DF714730AA9005E8FD4 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = DCC29DF514730AA9005E8FD4 /* MainMenu.xib */; };
+ DCC29E1F14730B05005E8FD4 /* BaseNSLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC29E1814730B05005E8FD4 /* BaseNSLogging.m */; };
+ DCC29E2014730B05005E8FD4 /* DynamicLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC29E1A14730B05005E8FD4 /* DynamicLogging.m */; };
+ DCC29E2114730B05005E8FD4 /* PerformanceTesting.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC29E1C14730B05005E8FD4 /* PerformanceTesting.m */; };
+ DCC29E2214730B05005E8FD4 /* StaticLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC29E1E14730B05005E8FD4 /* StaticLogging.m */; };
+ F434A3AE9AE44303AA5DB377 /* libPods-BM_osx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B87C32CF2EF4298BD9B0199 /* libPods-BM_osx.a */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 3B87C32CF2EF4298BD9B0199 /* libPods-BM_osx.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-BM_osx.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 5EE3DC55E808453403A4E009 /* Pods-BM_osx.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BM_osx.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BM_osx/Pods-BM_osx.debug.xcconfig"; sourceTree = "<group>"; };
+ A4CB8540E4C50155D1D220D9 /* Pods-BM_osx.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BM_osx.release.xcconfig"; path = "Pods/Target Support Files/Pods-BM_osx/Pods-BM_osx.release.xcconfig"; sourceTree = "<group>"; };
+ DCC29DDC14730AA9005E8FD4 /* BenchmarkMac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BenchmarkMac.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ DCC29DE014730AA9005E8FD4 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
+ DCC29DE314730AA9005E8FD4 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
+ DCC29DE414730AA9005E8FD4 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
+ DCC29DE514730AA9005E8FD4 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+ DCC29DE814730AA9005E8FD4 /* BenchmarkMac-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "BenchmarkMac-Info.plist"; sourceTree = "<group>"; };
+ DCC29DEA14730AA9005E8FD4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+ DCC29DEC14730AA9005E8FD4 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ DCC29DEE14730AA9005E8FD4 /* BenchmarkMac-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BenchmarkMac-Prefix.pch"; sourceTree = "<group>"; };
+ DCC29DF014730AA9005E8FD4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = "<group>"; };
+ DCC29DF214730AA9005E8FD4 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+ DCC29DF314730AA9005E8FD4 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+ DCC29DF614730AA9005E8FD4 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = "<group>"; };
+ DCC29E1714730B05005E8FD4 /* BaseNSLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BaseNSLogging.h; sourceTree = "<group>"; };
+ DCC29E1814730B05005E8FD4 /* BaseNSLogging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BaseNSLogging.m; sourceTree = "<group>"; };
+ DCC29E1914730B05005E8FD4 /* DynamicLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DynamicLogging.h; sourceTree = "<group>"; };
+ DCC29E1A14730B05005E8FD4 /* DynamicLogging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DynamicLogging.m; sourceTree = "<group>"; };
+ DCC29E1B14730B05005E8FD4 /* PerformanceTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PerformanceTesting.h; sourceTree = "<group>"; };
+ DCC29E1C14730B05005E8FD4 /* PerformanceTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PerformanceTesting.m; sourceTree = "<group>"; };
+ DCC29E1D14730B05005E8FD4 /* StaticLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StaticLogging.h; sourceTree = "<group>"; };
+ DCC29E1E14730B05005E8FD4 /* StaticLogging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StaticLogging.m; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ DCC29DD914730AA9005E8FD4 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DCC29DE114730AA9005E8FD4 /* Cocoa.framework in Frameworks */,
+ F434A3AE9AE44303AA5DB377 /* libPods-BM_osx.a in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 8B2D89AB0547F7D910983389 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 5EE3DC55E808453403A4E009 /* Pods-BM_osx.debug.xcconfig */,
+ A4CB8540E4C50155D1D220D9 /* Pods-BM_osx.release.xcconfig */,
+ );
+ name = Pods;
+ sourceTree = "<group>";
+ };
+ DCC29DD114730AA9005E8FD4 = {
+ isa = PBXGroup;
+ children = (
+ DCC29E1614730AC6005E8FD4 /* Benchmarking */,
+ DCC29DE614730AA9005E8FD4 /* BenchmarkMac */,
+ DCC29DDF14730AA9005E8FD4 /* Frameworks */,
+ DCC29DDD14730AA9005E8FD4 /* Products */,
+ 8B2D89AB0547F7D910983389 /* Pods */,
+ );
+ sourceTree = "<group>";
+ };
+ DCC29DDD14730AA9005E8FD4 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ DCC29DDC14730AA9005E8FD4 /* BenchmarkMac.app */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ DCC29DDF14730AA9005E8FD4 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ DCC29DE014730AA9005E8FD4 /* Cocoa.framework */,
+ DCC29DE214730AA9005E8FD4 /* Other Frameworks */,
+ 3B87C32CF2EF4298BD9B0199 /* libPods-BM_osx.a */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ DCC29DE214730AA9005E8FD4 /* Other Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ DCC29DE314730AA9005E8FD4 /* AppKit.framework */,
+ DCC29DE414730AA9005E8FD4 /* CoreData.framework */,
+ DCC29DE514730AA9005E8FD4 /* Foundation.framework */,
+ );
+ name = "Other Frameworks";
+ sourceTree = "<group>";
+ };
+ DCC29DE614730AA9005E8FD4 /* BenchmarkMac */ = {
+ isa = PBXGroup;
+ children = (
+ DCC29DF214730AA9005E8FD4 /* AppDelegate.h */,
+ DCC29DF314730AA9005E8FD4 /* AppDelegate.m */,
+ DCC29DF514730AA9005E8FD4 /* MainMenu.xib */,
+ DCC29DE714730AA9005E8FD4 /* Supporting Files */,
+ );
+ path = BenchmarkMac;
+ sourceTree = "<group>";
+ };
+ DCC29DE714730AA9005E8FD4 /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ DCC29DE814730AA9005E8FD4 /* BenchmarkMac-Info.plist */,
+ DCC29DE914730AA9005E8FD4 /* InfoPlist.strings */,
+ DCC29DEC14730AA9005E8FD4 /* main.m */,
+ DCC29DEE14730AA9005E8FD4 /* BenchmarkMac-Prefix.pch */,
+ DCC29DEF14730AA9005E8FD4 /* Credits.rtf */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
+ DCC29E1614730AC6005E8FD4 /* Benchmarking */ = {
+ isa = PBXGroup;
+ children = (
+ DCC29E1714730B05005E8FD4 /* BaseNSLogging.h */,
+ DCC29E1814730B05005E8FD4 /* BaseNSLogging.m */,
+ DCC29E1914730B05005E8FD4 /* DynamicLogging.h */,
+ DCC29E1A14730B05005E8FD4 /* DynamicLogging.m */,
+ DCC29E1B14730B05005E8FD4 /* PerformanceTesting.h */,
+ DCC29E1C14730B05005E8FD4 /* PerformanceTesting.m */,
+ DCC29E1D14730B05005E8FD4 /* StaticLogging.h */,
+ DCC29E1E14730B05005E8FD4 /* StaticLogging.m */,
+ );
+ name = Benchmarking;
+ path = ../../../Benchmarking;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ DCC29DDB14730AA9005E8FD4 /* BenchmarkMac */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DCC29DFA14730AA9005E8FD4 /* Build configuration list for PBXNativeTarget "BenchmarkMac" */;
+ buildPhases = (
+ CCC3BE78F0DF486A86ECB56A /* Check Pods Manifest.lock */,
+ DCC29DD814730AA9005E8FD4 /* Sources */,
+ DCC29DD914730AA9005E8FD4 /* Frameworks */,
+ DCC29DDA14730AA9005E8FD4 /* Resources */,
+ 8506515FE7184EE6BF1F427F /* Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = BenchmarkMac;
+ productName = BenchmarkMac;
+ productReference = DCC29DDC14730AA9005E8FD4 /* BenchmarkMac.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ DCC29DD314730AA9005E8FD4 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0610;
+ };
+ buildConfigurationList = DCC29DD614730AA9005E8FD4 /* Build configuration list for PBXProject "BenchmarkMac" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = DCC29DD114730AA9005E8FD4;
+ productRefGroup = DCC29DDD14730AA9005E8FD4 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ DCC29DDB14730AA9005E8FD4 /* BenchmarkMac */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ DCC29DDA14730AA9005E8FD4 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DCC29DEB14730AA9005E8FD4 /* InfoPlist.strings in Resources */,
+ DCC29DF114730AA9005E8FD4 /* Credits.rtf in Resources */,
+ DCC29DF714730AA9005E8FD4 /* MainMenu.xib in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 8506515FE7184EE6BF1F427F /* Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-BM_osx/Pods-BM_osx-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ CCC3BE78F0DF486A86ECB56A /* Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Check Pods Manifest.lock";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ DCC29DD814730AA9005E8FD4 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DCC29DED14730AA9005E8FD4 /* main.m in Sources */,
+ DCC29DF414730AA9005E8FD4 /* AppDelegate.m in Sources */,
+ DCC29E1F14730B05005E8FD4 /* BaseNSLogging.m in Sources */,
+ DCC29E2014730B05005E8FD4 /* DynamicLogging.m in Sources */,
+ DCC29E2114730B05005E8FD4 /* PerformanceTesting.m in Sources */,
+ DCC29E2214730B05005E8FD4 /* StaticLogging.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ DCC29DE914730AA9005E8FD4 /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DCC29DEA14730AA9005E8FD4 /* en */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "<group>";
+ };
+ DCC29DEF14730AA9005E8FD4 /* Credits.rtf */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DCC29DF014730AA9005E8FD4 /* en */,
+ );
+ name = Credits.rtf;
+ sourceTree = "<group>";
+ };
+ DCC29DF514730AA9005E8FD4 /* MainMenu.xib */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DCC29DF614730AA9005E8FD4 /* en */,
+ );
+ name = MainMenu.xib;
+ sourceTree = "<group>";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ DCC29DF814730AA9005E8FD4 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.7;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = macosx;
+ };
+ name = Debug;
+ };
+ DCC29DF914730AA9005E8FD4 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = YES;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+ GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.7;
+ SDKROOT = macosx;
+ };
+ name = Release;
+ };
+ DCC29DFB14730AA9005E8FD4 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 5EE3DC55E808453403A4E009 /* Pods-BM_osx.debug.xcconfig */;
+ buildSettings = {
+ COMBINE_HIDPI_IMAGES = YES;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "BenchmarkMac/BenchmarkMac-Prefix.pch";
+ INFOPLIST_FILE = "BenchmarkMac/BenchmarkMac-Info.plist";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ WRAPPER_EXTENSION = app;
+ };
+ name = Debug;
+ };
+ DCC29DFC14730AA9005E8FD4 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = A4CB8540E4C50155D1D220D9 /* Pods-BM_osx.release.xcconfig */;
+ buildSettings = {
+ COMBINE_HIDPI_IMAGES = YES;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "BenchmarkMac/BenchmarkMac-Prefix.pch";
+ INFOPLIST_FILE = "BenchmarkMac/BenchmarkMac-Info.plist";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ WRAPPER_EXTENSION = app;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ DCC29DD614730AA9005E8FD4 /* Build configuration list for PBXProject "BenchmarkMac" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DCC29DF814730AA9005E8FD4 /* Debug */,
+ DCC29DF914730AA9005E8FD4 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DCC29DFA14730AA9005E8FD4 /* Build configuration list for PBXNativeTarget "BenchmarkMac" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DCC29DFB14730AA9005E8FD4 /* Debug */,
+ DCC29DFC14730AA9005E8FD4 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = DCC29DD314730AA9005E8FD4 /* Project object */;
+}
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/xcshareddata/._xcschemes b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/xcshareddata/._xcschemes
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/xcshareddata/._xcschemes
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/xcshareddata/xcschemes/._BenchmarkMac.xcscheme b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/xcshareddata/xcschemes/._BenchmarkMac.xcscheme
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/xcshareddata/xcschemes/._BenchmarkMac.xcscheme
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/xcshareddata/xcschemes/BenchmarkMac.xcscheme b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/xcshareddata/xcschemes/BenchmarkMac.xcscheme
new file mode 100644
index 0000000..4aa3410
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac.xcodeproj/xcshareddata/xcschemes/BenchmarkMac.xcscheme
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0610"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DCC29DDB14730AA9005E8FD4"
+ BuildableName = "BenchmarkMac.app"
+ BlueprintName = "BenchmarkMac"
+ ReferencedContainer = "container:BenchmarkMac.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ buildConfiguration = "Debug">
+ <Testables>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DCC29DDB14730AA9005E8FD4"
+ BuildableName = "BenchmarkMac.app"
+ BlueprintName = "BenchmarkMac"
+ ReferencedContainer = "container:BenchmarkMac.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ </TestAction>
+ <LaunchAction
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ buildConfiguration = "Debug"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DCC29DDB14730AA9005E8FD4"
+ BuildableName = "BenchmarkMac.app"
+ BlueprintName = "BenchmarkMac"
+ ReferencedContainer = "container:BenchmarkMac.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ buildConfiguration = "Release"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DCC29DDB14730AA9005E8FD4"
+ BuildableName = "BenchmarkMac.app"
+ BlueprintName = "BenchmarkMac"
+ ReferencedContainer = "container:BenchmarkMac.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._AppDelegate.h b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._AppDelegate.h
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._AppDelegate.h
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._AppDelegate.m b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._AppDelegate.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._AppDelegate.m
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._BenchmarkMac-Info.plist b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._BenchmarkMac-Info.plist
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._BenchmarkMac-Info.plist
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._BenchmarkMac-Prefix.pch b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._BenchmarkMac-Prefix.pch
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._BenchmarkMac-Prefix.pch
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._en.lproj b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._en.lproj
new file mode 100755
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._en.lproj
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._main.m b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._main.m
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/._main.m
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/AppDelegate.h b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/AppDelegate.h
new file mode 100644
index 0000000..c295a70
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/AppDelegate.h
@@ -0,0 +1,14 @@
+//
+// AppDelegate.h
+// BenchmarkMac
+//
+// Created by Robbie Hanson on 11/15/11.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface AppDelegate : NSObject <NSApplicationDelegate>
+
+@property (assign) IBOutlet NSWindow *window;
+
+@end
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/AppDelegate.m b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/AppDelegate.m
new file mode 100644
index 0000000..42842ea
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/AppDelegate.m
@@ -0,0 +1,14 @@
+#import "AppDelegate.h"
+#import "PerformanceTesting.h"
+
+
+@implementation AppDelegate
+
+@synthesize window = _window;
+
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
+{
+ [PerformanceTesting startPerformanceTests];
+}
+
+@end
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/BenchmarkMac-Info.plist b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/BenchmarkMac-Info.plist
new file mode 100644
index 0000000..6220f93
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/BenchmarkMac-Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIconFile</key>
+ <string></string>
+ <key>CFBundleIdentifier</key>
+ <string>com.deusty.oss.${PRODUCT_NAME:rfc1034identifier}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>${MACOSX_DEPLOYMENT_TARGET}</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/BenchmarkMac-Prefix.pch b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/BenchmarkMac-Prefix.pch
new file mode 100644
index 0000000..4cbf974
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/BenchmarkMac-Prefix.pch
@@ -0,0 +1,7 @@
+//
+// Prefix header for all source files of the 'BenchmarkMac' target in the 'BenchmarkMac' project
+//
+
+#ifdef __OBJC__
+ #import <Cocoa/Cocoa.h>
+#endif
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/._Credits.rtf b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/._Credits.rtf
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/._Credits.rtf
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/._InfoPlist.strings b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/._InfoPlist.strings
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/._InfoPlist.strings
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/._MainMenu.xib b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/._MainMenu.xib
new file mode 100644
index 0000000..bc71f26
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/._MainMenu.xib
Binary files differ
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/Credits.rtf b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/Credits.rtf
new file mode 100644
index 0000000..46576ef
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/Credits.rtf
@@ -0,0 +1,29 @@
+{\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\paperw9840\paperh8400
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
+
+\f0\b\fs24 \cf0 Engineering:
+\b0 \
+ Some people\
+\
+
+\b Human Interface Design:
+\b0 \
+ Some other people\
+\
+
+\b Testing:
+\b0 \
+ Hopefully not nobody\
+\
+
+\b Documentation:
+\b0 \
+ Whoever\
+\
+
+\b With special thanks to:
+\b0 \
+ Mom\
+}
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/InfoPlist.strings b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/InfoPlist.strings
new file mode 100644
index 0000000..477b28f
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/InfoPlist.strings
@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+
diff --git a/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/MainMenu.xib b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/MainMenu.xib
new file mode 100644
index 0000000..18f1a3e
--- /dev/null
+++ b/cocoalumberjack/Demos/Benchmark/Desktop/BenchmarkMac/en.lproj/MainMenu.xib
@@ -0,0 +1,4582 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
+ <data>
+ <int key="IBDocument.SystemTarget">1070</int>
+ <string key="IBDocument.SystemVersion">11A511</string>
+ <string key="IBDocument.InterfaceBuilderVersion">1920</string>
+ <string key="IBDocument.AppKitVersion">1138</string>
+ <string key="IBDocument.HIToolboxVersion">566.00</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="NS.object.0">1920</string>
+ </object>
+ <array key="IBDocument.IntegratedClassDependencies">
+ <string>NSWindowTemplate</string>
+ <string>NSView</string>
+ <string>NSMenu</string>
+ <string>NSMenuItem</string>
+ <string>NSCustomObject</string>
+ </array>
+ <array key="IBDocument.PluginDependencies">
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ </array>
+ <object class="NSMutableDictionary" key="IBDocument.Metadata">
+ <string key="NS.key.0">PluginDependencyRecalculationVersion</string>
+ <integer value="1" key="NS.object.0"/>
+ </object>
+ <array class="NSMutableArray" key="IBDocument.RootObjects" id="1048">
+ <object class="NSCustomObject" id="1021">
+ <string key="NSClassName">NSApplication</string>
+ </object>
+ <object class="NSCustomObject" id="1014">
+ <string key="NSClassName">FirstResponder</string>
+ </object>
+ <object class="NSCustomObject" id="1050">
+ <string key="NSClassName">NSApplication</string>
+ </object>
+ <object class="NSMenu" id="649796088">
+ <string key="NSTitle">AMainMenu</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="694149608">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">BenchmarkMac</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <object class="NSCustomResource" key="NSOnImage" id="35465992">
+ <string key="NSClassName">NSImage</string>
+ <string key="NSResourceName">NSMenuCheckmark</string>
+ </object>
+ <object class="NSCustomResource" key="NSMixedImage" id="502551668">
+ <string key="NSClassName">NSImage</string>
+ <string key="NSResourceName">NSMenuMixedState</string>
+ </object>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="110575045">
+ <string key="NSTitle">BenchmarkMac</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="238522557">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">About BenchmarkMac</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="304266470">
+ <reference key="NSMenu" ref="110575045"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="609285721">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Preferences…</string>
+ <string key="NSKeyEquiv">,</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="481834944">
+ <reference key="NSMenu" ref="110575045"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1046388886">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Services</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="752062318">
+ <string key="NSTitle">Services</string>
+ <array class="NSMutableArray" key="NSMenuItems"/>
+ <string key="NSName">_NSServicesMenu</string>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="646227648">
+ <reference key="NSMenu" ref="110575045"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="755159360">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Hide BenchmarkMac</string>
+ <string key="NSKeyEquiv">h</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="342932134">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Hide Others</string>
+ <string key="NSKeyEquiv">h</string>
+ <int key="NSKeyEquivModMask">1572864</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="908899353">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Show All</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1056857174">
+ <reference key="NSMenu" ref="110575045"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="632727374">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Quit BenchmarkMac</string>
+ <string key="NSKeyEquiv">q</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ <string key="NSName">_NSAppleMenu</string>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="379814623">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">File</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="720053764">
+ <string key="NSTitle">File</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="705341025">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">New</string>
+ <string key="NSKeyEquiv">n</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="722745758">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Open…</string>
+ <string key="NSKeyEquiv">o</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1025936716">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Open Recent</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="1065607017">
+ <string key="NSTitle">Open Recent</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="759406840">
+ <reference key="NSMenu" ref="1065607017"/>
+ <string key="NSTitle">Clear Menu</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ <string key="NSName">_NSRecentDocumentsMenu</string>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="425164168">
+ <reference key="NSMenu" ref="720053764"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="776162233">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Close</string>
+ <string key="NSKeyEquiv">w</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1023925487">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Save…</string>
+ <string key="NSKeyEquiv">s</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="579971712">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Revert to Saved</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1010469920">
+ <reference key="NSMenu" ref="720053764"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="294629803">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Page Setup...</string>
+ <string key="NSKeyEquiv">P</string>
+ <int key="NSKeyEquivModMask">1179648</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSToolTip"/>
+ </object>
+ <object class="NSMenuItem" id="49223823">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Print…</string>
+ <string key="NSKeyEquiv">p</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="952259628">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">Edit</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="789758025">
+ <string key="NSTitle">Edit</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="1058277027">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Undo</string>
+ <string key="NSKeyEquiv">z</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="790794224">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Redo</string>
+ <string key="NSKeyEquiv">Z</string>
+ <int key="NSKeyEquivModMask">1179648</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1040322652">
+ <reference key="NSMenu" ref="789758025"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="296257095">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Cut</string>
+ <string key="NSKeyEquiv">x</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="860595796">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Copy</string>
+ <string key="NSKeyEquiv">c</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="29853731">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Paste</string>
+ <string key="NSKeyEquiv">v</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="82994268">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Paste and Match Style</string>
+ <string key="NSKeyEquiv">V</string>
+ <int key="NSKeyEquivModMask">1572864</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="437104165">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Delete</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="583158037">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Select All</string>
+ <string key="NSKeyEquiv">a</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="212016141">
+ <reference key="NSMenu" ref="789758025"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="892235320">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Find</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="963351320">
+ <string key="NSTitle">Find</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="447796847">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Find…</string>
+ <string key="NSKeyEquiv">f</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">1</int>
+ </object>
+ <object class="NSMenuItem" id="738670835">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Find and Replace…</string>
+ <string key="NSKeyEquiv">f</string>
+ <int key="NSKeyEquivModMask">1572864</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">12</int>
+ </object>
+ <object class="NSMenuItem" id="326711663">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Find Next</string>
+ <string key="NSKeyEquiv">g</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">2</int>
+ </object>
+ <object class="NSMenuItem" id="270902937">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Find Previous</string>
+ <string key="NSKeyEquiv">G</string>
+ <int key="NSKeyEquivModMask">1179648</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">3</int>
+ </object>
+ <object class="NSMenuItem" id="159080638">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Use Selection for Find</string>
+ <string key="NSKeyEquiv">e</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">7</int>
+ </object>
+ <object class="NSMenuItem" id="88285865">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Jump to Selection</string>
+ <string key="NSKeyEquiv">j</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="972420730">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Spelling and Grammar</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="769623530">
+ <string key="NSTitle">Spelling and Grammar</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="679648819">
+ <reference key="NSMenu" ref="769623530"/>
+ <string key="NSTitle">Show Spelling and Grammar</string>
+ <string key="NSKeyEquiv">:</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="96193923">
+ <reference key="NSMenu" ref="769623530"/>
+ <string key="NSTitle">Check Document Now</string>
+ <string key="NSKeyEquiv">;</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="859480356">
+ <reference key="NSMenu" ref="769623530"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="948374510">
+ <reference key="NSMenu" ref="769623530"/>
+ <string key="NSTitle">Check Spelling While Typing</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="967646866">
+ <reference key="NSMenu" ref="769623530"/>
+ <string key="NSTitle">Check Grammar With Spelling</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="795346622">
+ <reference key="NSMenu" ref="769623530"/>
+ <string key="NSTitle">Correct Spelling Automatically</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="507821607">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Substitutions</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="698887838">
+ <string key="NSTitle">Substitutions</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="65139061">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Show Substitutions</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="19036812">
+ <reference key="NSMenu" ref="698887838"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="605118523">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Smart Copy/Paste</string>
+ <string key="NSKeyEquiv">f</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">1</int>
+ </object>
+ <object class="NSMenuItem" id="197661976">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Smart Quotes</string>
+ <string key="NSKeyEquiv">g</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">2</int>
+ </object>
+ <object class="NSMenuItem" id="672708820">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Smart Dashes</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="708854459">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Smart Links</string>
+ <string key="NSKeyEquiv">G</string>
+ <int key="NSKeyEquivModMask">1179648</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">3</int>
+ </object>
+ <object class="NSMenuItem" id="537092702">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Text Replacement</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="288088188">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Transformations</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="579392910">
+ <string key="NSTitle">Transformations</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="1060694897">
+ <reference key="NSMenu" ref="579392910"/>
+ <string key="NSTitle">Make Upper Case</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="879586729">
+ <reference key="NSMenu" ref="579392910"/>
+ <string key="NSTitle">Make Lower Case</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="56570060">
+ <reference key="NSMenu" ref="579392910"/>
+ <string key="NSTitle">Capitalize</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="676164635">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Speech</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="785027613">
+ <string key="NSTitle">Speech</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="731782645">
+ <reference key="NSMenu" ref="785027613"/>
+ <string key="NSTitle">Start Speaking</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="680220178">
+ <reference key="NSMenu" ref="785027613"/>
+ <string key="NSTitle">Stop Speaking</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="302598603">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">Format</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="941447902">
+ <string key="NSTitle">Format</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="792887677">
+ <reference key="NSMenu" ref="941447902"/>
+ <string key="NSTitle">Font</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="786677654">
+ <string key="NSTitle">Font</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="159677712">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Show Fonts</string>
+ <string key="NSKeyEquiv">t</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="305399458">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Bold</string>
+ <string key="NSKeyEquiv">b</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">2</int>
+ </object>
+ <object class="NSMenuItem" id="814362025">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Italic</string>
+ <string key="NSKeyEquiv">i</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">1</int>
+ </object>
+ <object class="NSMenuItem" id="330926929">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Underline</string>
+ <string key="NSKeyEquiv">u</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="533507878">
+ <reference key="NSMenu" ref="786677654"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="158063935">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Bigger</string>
+ <string key="NSKeyEquiv">+</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">3</int>
+ </object>
+ <object class="NSMenuItem" id="885547335">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Smaller</string>
+ <string key="NSKeyEquiv">-</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">4</int>
+ </object>
+ <object class="NSMenuItem" id="901062459">
+ <reference key="NSMenu" ref="786677654"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="767671776">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Kern</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="175441468">
+ <string key="NSTitle">Kern</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="252969304">
+ <reference key="NSMenu" ref="175441468"/>
+ <string key="NSTitle">Use Default</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="766922938">
+ <reference key="NSMenu" ref="175441468"/>
+ <string key="NSTitle">Use None</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="677519740">
+ <reference key="NSMenu" ref="175441468"/>
+ <string key="NSTitle">Tighten</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="238351151">
+ <reference key="NSMenu" ref="175441468"/>
+ <string key="NSTitle">Loosen</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="691570813">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Ligature</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="1058217995">
+ <string key="NSTitle">Ligature</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="706297211">
+ <reference key="NSMenu" ref="1058217995"/>
+ <string key="NSTitle">Use Default</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="568384683">
+ <reference key="NSMenu" ref="1058217995"/>
+ <string key="NSTitle">Use None</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="663508465">
+ <reference key="NSMenu" ref="1058217995"/>
+ <string key="NSTitle">Use All</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="769124883">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Baseline</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="18263474">
+ <string key="NSTitle">Baseline</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="257962622">
+ <reference key="NSMenu" ref="18263474"/>
+ <string key="NSTitle">Use Default</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="644725453">
+ <reference key="NSMenu" ref="18263474"/>
+ <string key="NSTitle">Superscript</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1037576581">
+ <reference key="NSMenu" ref="18263474"/>
+ <string key="NSTitle">Subscript</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="941806246">
+ <reference key="NSMenu" ref="18263474"/>
+ <string key="NSTitle">Raise</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1045724900">
+ <reference key="NSMenu" ref="18263474"/>
+ <string key="NSTitle">Lower</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="739652853">
+ <reference key="NSMenu" ref="786677654"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1012600125">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Show Colors</string>
+ <string key="NSKeyEquiv">C</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="214559597">
+ <reference key="NSMenu" ref="786677654"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="596732606">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Copy Style</string>
+ <string key="NSKeyEquiv">c</string>
+ <int key="NSKeyEquivModMask">1572864</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="393423671">
+ <reference key="NSMenu" ref="786677654"/>
+ <string key="NSTitle">Paste Style</string>
+ <string key="NSKeyEquiv">v</string>
+ <int key="NSKeyEquivModMask">1572864</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ <string key="NSName">_NSFontMenu</string>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="215659978">
+ <reference key="NSMenu" ref="941447902"/>
+ <string key="NSTitle">Text</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="446991534">
+ <string key="NSTitle">Text</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="875092757">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Align Left</string>
+ <string key="NSKeyEquiv">{</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="630155264">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Center</string>
+ <string key="NSKeyEquiv">|</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="945678886">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Justify</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="512868991">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Align Right</string>
+ <string key="NSKeyEquiv">}</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="163117631">
+ <reference key="NSMenu" ref="446991534"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="31516759">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Writing Direction</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="956096989">
+ <string key="NSTitle">Writing Direction</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="257099033">
+ <reference key="NSMenu" ref="956096989"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <string key="NSTitle">Paragraph</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="551969625">
+ <reference key="NSMenu" ref="956096989"/>
+ <string type="base64-UTF8" key="NSTitle">CURlZmF1bHQ</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="249532473">
+ <reference key="NSMenu" ref="956096989"/>
+ <string type="base64-UTF8" key="NSTitle">CUxlZnQgdG8gUmlnaHQ</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="607364498">
+ <reference key="NSMenu" ref="956096989"/>
+ <string type="base64-UTF8" key="NSTitle">CVJpZ2h0IHRvIExlZnQ</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="508151438">
+ <reference key="NSMenu" ref="956096989"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="981751889">
+ <reference key="NSMenu" ref="956096989"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <string key="NSTitle">Selection</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="380031999">
+ <reference key="NSMenu" ref="956096989"/>
+ <string type="base64-UTF8" key="NSTitle">CURlZmF1bHQ</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="825984362">
+ <reference key="NSMenu" ref="956096989"/>
+ <string type="base64-UTF8" key="NSTitle">CUxlZnQgdG8gUmlnaHQ</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="560145579">
+ <reference key="NSMenu" ref="956096989"/>
+ <string type="base64-UTF8" key="NSTitle">CVJpZ2h0IHRvIExlZnQ</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="908105787">
+ <reference key="NSMenu" ref="446991534"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="644046920">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Show Ruler</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="231811626">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Copy Ruler</string>
+ <string key="NSKeyEquiv">c</string>
+ <int key="NSKeyEquivModMask">1310720</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="883618387">
+ <reference key="NSMenu" ref="446991534"/>
+ <string key="NSTitle">Paste Ruler</string>
+ <string key="NSKeyEquiv">v</string>
+ <int key="NSKeyEquivModMask">1310720</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="586577488">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">View</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="466310130">
+ <string key="NSTitle">View</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="102151532">
+ <reference key="NSMenu" ref="466310130"/>
+ <string key="NSTitle">Show Toolbar</string>
+ <string key="NSKeyEquiv">t</string>
+ <int key="NSKeyEquivModMask">1572864</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="237841660">
+ <reference key="NSMenu" ref="466310130"/>
+ <string key="NSTitle">Customize Toolbar…</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="713487014">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">Window</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="835318025">
+ <string key="NSTitle">Window</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="1011231497">
+ <reference key="NSMenu" ref="835318025"/>
+ <string key="NSTitle">Minimize</string>
+ <string key="NSKeyEquiv">m</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="575023229">
+ <reference key="NSMenu" ref="835318025"/>
+ <string key="NSTitle">Zoom</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="299356726">
+ <reference key="NSMenu" ref="835318025"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="625202149">
+ <reference key="NSMenu" ref="835318025"/>
+ <string key="NSTitle">Bring All to Front</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ <string key="NSName">_NSWindowsMenu</string>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="448692316">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">Help</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="992780483">
+ <string key="NSTitle">Help</string>
+ <array class="NSMutableArray" key="NSMenuItems">
+ <object class="NSMenuItem" id="105068016">
+ <reference key="NSMenu" ref="992780483"/>
+ <string key="NSTitle">BenchmarkMac Help</string>
+ <string key="NSKeyEquiv">?</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </array>
+ <string key="NSName">_NSHelpMenu</string>
+ </object>
+ </object>
+ </array>
+ <string key="NSName">_NSMainMenu</string>
+ </object>
+ <object class="NSWindowTemplate" id="972006081">
+ <int key="NSWindowStyleMask">15</int>
+ <int key="NSWindowBacking">2</int>
+ <string key="NSWindowRect">{{335, 390}, {480, 360}}</string>
+ <int key="NSWTFlags">1954021376</int>
+ <string key="NSWindowTitle">BenchmarkMac</string>
+ <string key="NSWindowClass">NSWindow</string>
+ <nil key="NSViewClass"/>
+ <nil key="NSUserInterfaceItemIdentifier"/>
+ <object class="NSView" key="NSWindowView" id="439893737">
+ <nil key="NSNextResponder"/>
+ <int key="NSvFlags">256</int>
+ <string key="NSFrameSize">{480, 360}</string>
+ </object>
+ <string key="NSScreenRect">{{0, 0}, {1920, 1178}}</string>
+ <string key="NSMaxSize">{10000000000000, 10000000000000}</string>
+ <bool key="NSWindowIsRestorable">YES</bool>
+ </object>
+ <object class="NSCustomObject" id="976324537">
+ <string key="NSClassName">AppDelegate</string>
+ </object>
+ <object class="NSCustomObject" id="755631768">
+ <string key="NSClassName">NSFontManager</string>
+ </object>
+ </array>
+ <object class="IBObjectContainer" key="IBDocument.Objects">
+ <array class="NSMutableArray" key="connectionRecords">
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">terminate:</string>
+ <reference key="source" ref="1050"/>
+ <reference key="destination" ref="632727374"/>
+ </object>
+ <int key="connectionID">449</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">orderFrontStandardAboutPanel:</string>
+ <reference key="source" ref="1021"/>
+ <reference key="destination" ref="238522557"/>
+ </object>
+ <int key="connectionID">142</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">delegate</string>
+ <reference key="source" ref="1021"/>
+ <reference key="destination" ref="976324537"/>
+ </object>
+ <int key="connectionID">495</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performMiniaturize:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1011231497"/>
+ </object>
+ <int key="connectionID">37</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">arrangeInFront:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="625202149"/>
+ </object>
+ <int key="connectionID">39</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">print:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="49223823"/>
+ </object>
+ <int key="connectionID">86</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">runPageLayout:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="294629803"/>
+ </object>
+ <int key="connectionID">87</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">clearRecentDocuments:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="759406840"/>
+ </object>
+ <int key="connectionID">127</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performClose:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="776162233"/>
+ </object>
+ <int key="connectionID">193</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleContinuousSpellChecking:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="948374510"/>
+ </object>
+ <int key="connectionID">222</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">undo:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1058277027"/>
+ </object>
+ <int key="connectionID">223</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">copy:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="860595796"/>
+ </object>
+ <int key="connectionID">224</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">checkSpelling:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="96193923"/>
+ </object>
+ <int key="connectionID">225</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">paste:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="29853731"/>
+ </object>
+ <int key="connectionID">226</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">stopSpeaking:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="680220178"/>
+ </object>
+ <int key="connectionID">227</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">cut:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="296257095"/>
+ </object>
+ <int key="connectionID">228</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">showGuessPanel:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="679648819"/>
+ </object>
+ <int key="connectionID">230</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">redo:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="790794224"/>
+ </object>
+ <int key="connectionID">231</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">selectAll:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="583158037"/>
+ </object>
+ <int key="connectionID">232</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">startSpeaking:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="731782645"/>
+ </object>
+ <int key="connectionID">233</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">delete:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="437104165"/>
+ </object>
+ <int key="connectionID">235</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performZoom:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="575023229"/>
+ </object>
+ <int key="connectionID">240</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performFindPanelAction:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="447796847"/>
+ </object>
+ <int key="connectionID">241</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">centerSelectionInVisibleArea:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="88285865"/>
+ </object>
+ <int key="connectionID">245</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleGrammarChecking:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="967646866"/>
+ </object>
+ <int key="connectionID">347</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleSmartInsertDelete:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="605118523"/>
+ </object>
+ <int key="connectionID">355</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleAutomaticQuoteSubstitution:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="197661976"/>
+ </object>
+ <int key="connectionID">356</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleAutomaticLinkDetection:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="708854459"/>
+ </object>
+ <int key="connectionID">357</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">saveDocument:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1023925487"/>
+ </object>
+ <int key="connectionID">362</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">revertDocumentToSaved:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="579971712"/>
+ </object>
+ <int key="connectionID">364</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">runToolbarCustomizationPalette:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="237841660"/>
+ </object>
+ <int key="connectionID">365</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleToolbarShown:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="102151532"/>
+ </object>
+ <int key="connectionID">366</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">hide:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="755159360"/>
+ </object>
+ <int key="connectionID">367</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">hideOtherApplications:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="342932134"/>
+ </object>
+ <int key="connectionID">368</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">unhideAllApplications:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="908899353"/>
+ </object>
+ <int key="connectionID">370</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">newDocument:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="705341025"/>
+ </object>
+ <int key="connectionID">373</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">openDocument:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="722745758"/>
+ </object>
+ <int key="connectionID">374</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">raiseBaseline:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="941806246"/>
+ </object>
+ <int key="connectionID">426</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">lowerBaseline:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1045724900"/>
+ </object>
+ <int key="connectionID">427</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">copyFont:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="596732606"/>
+ </object>
+ <int key="connectionID">428</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">subscript:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1037576581"/>
+ </object>
+ <int key="connectionID">429</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">superscript:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="644725453"/>
+ </object>
+ <int key="connectionID">430</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">tightenKerning:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="677519740"/>
+ </object>
+ <int key="connectionID">431</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">underline:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="330926929"/>
+ </object>
+ <int key="connectionID">432</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">orderFrontColorPanel:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1012600125"/>
+ </object>
+ <int key="connectionID">433</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">useAllLigatures:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="663508465"/>
+ </object>
+ <int key="connectionID">434</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">loosenKerning:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="238351151"/>
+ </object>
+ <int key="connectionID">435</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">pasteFont:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="393423671"/>
+ </object>
+ <int key="connectionID">436</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">unscript:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="257962622"/>
+ </object>
+ <int key="connectionID">437</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">useStandardKerning:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="252969304"/>
+ </object>
+ <int key="connectionID">438</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">useStandardLigatures:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="706297211"/>
+ </object>
+ <int key="connectionID">439</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">turnOffLigatures:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="568384683"/>
+ </object>
+ <int key="connectionID">440</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">turnOffKerning:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="766922938"/>
+ </object>
+ <int key="connectionID">441</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleAutomaticSpellingCorrection:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="795346622"/>
+ </object>
+ <int key="connectionID">456</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">orderFrontSubstitutionsPanel:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="65139061"/>
+ </object>
+ <int key="connectionID">458</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleAutomaticDashSubstitution:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="672708820"/>
+ </object>
+ <int key="connectionID">461</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleAutomaticTextReplacement:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="537092702"/>
+ </object>
+ <int key="connectionID">463</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">uppercaseWord:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1060694897"/>
+ </object>
+ <int key="connectionID">464</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">capitalizeWord:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="56570060"/>
+ </object>
+ <int key="connectionID">467</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">lowercaseWord:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="879586729"/>
+ </object>
+ <int key="connectionID">468</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">pasteAsPlainText:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="82994268"/>
+ </object>
+ <int key="connectionID">486</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performFindPanelAction:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="326711663"/>
+ </object>
+ <int key="connectionID">487</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performFindPanelAction:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="270902937"/>
+ </object>
+ <int key="connectionID">488</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performFindPanelAction:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="159080638"/>
+ </object>
+ <int key="connectionID">489</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">showHelp:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="105068016"/>
+ </object>
+ <int key="connectionID">493</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">alignCenter:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="630155264"/>
+ </object>
+ <int key="connectionID">518</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">pasteRuler:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="883618387"/>
+ </object>
+ <int key="connectionID">519</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleRuler:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="644046920"/>
+ </object>
+ <int key="connectionID">520</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">alignRight:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="512868991"/>
+ </object>
+ <int key="connectionID">521</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">copyRuler:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="231811626"/>
+ </object>
+ <int key="connectionID">522</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">alignJustified:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="945678886"/>
+ </object>
+ <int key="connectionID">523</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">alignLeft:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="875092757"/>
+ </object>
+ <int key="connectionID">524</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">makeBaseWritingDirectionNatural:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="551969625"/>
+ </object>
+ <int key="connectionID">525</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">makeBaseWritingDirectionLeftToRight:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="249532473"/>
+ </object>
+ <int key="connectionID">526</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">makeBaseWritingDirectionRightToLeft:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="607364498"/>
+ </object>
+ <int key="connectionID">527</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">makeTextWritingDirectionNatural:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="380031999"/>
+ </object>
+ <int key="connectionID">528</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">makeTextWritingDirectionLeftToRight:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="825984362"/>
+ </object>
+ <int key="connectionID">529</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">makeTextWritingDirectionRightToLeft:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="560145579"/>
+ </object>
+ <int key="connectionID">530</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performFindPanelAction:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="738670835"/>
+ </object>
+ <int key="connectionID">535</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">addFontTrait:</string>
+ <reference key="source" ref="755631768"/>
+ <reference key="destination" ref="305399458"/>
+ </object>
+ <int key="connectionID">421</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">addFontTrait:</string>
+ <reference key="source" ref="755631768"/>
+ <reference key="destination" ref="814362025"/>
+ </object>
+ <int key="connectionID">422</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">modifyFont:</string>
+ <reference key="source" ref="755631768"/>
+ <reference key="destination" ref="885547335"/>
+ </object>
+ <int key="connectionID">423</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">orderFrontFontPanel:</string>
+ <reference key="source" ref="755631768"/>
+ <reference key="destination" ref="159677712"/>
+ </object>
+ <int key="connectionID">424</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">modifyFont:</string>
+ <reference key="source" ref="755631768"/>
+ <reference key="destination" ref="158063935"/>
+ </object>
+ <int key="connectionID">425</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">window</string>
+ <reference key="source" ref="976324537"/>
+ <reference key="destination" ref="972006081"/>
+ </object>
+ <int key="connectionID">532</int>
+ </object>
+ </array>
+ <object class="IBMutableOrderedSet" key="objectRecords">
+ <array key="orderedObjects">
+ <object class="IBObjectRecord">
+ <int key="objectID">0</int>
+ <array key="object" id="0"/>
+ <reference key="children" ref="1048"/>
+ <nil key="parent"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-2</int>
+ <reference key="object" ref="1021"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">File's Owner</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-1</int>
+ <reference key="object" ref="1014"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">First Responder</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-3</int>
+ <reference key="object" ref="1050"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">Application</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">29</int>
+ <reference key="object" ref="649796088"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="713487014"/>
+ <reference ref="694149608"/>
+ <reference ref="952259628"/>
+ <reference ref="379814623"/>
+ <reference ref="586577488"/>
+ <reference ref="302598603"/>
+ <reference ref="448692316"/>
+ </array>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">19</int>
+ <reference key="object" ref="713487014"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="835318025"/>
+ </array>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">56</int>
+ <reference key="object" ref="694149608"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="110575045"/>
+ </array>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">217</int>
+ <reference key="object" ref="952259628"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="789758025"/>
+ </array>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">83</int>
+ <reference key="object" ref="379814623"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="720053764"/>
+ </array>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">81</int>
+ <reference key="object" ref="720053764"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="1023925487"/>
+ <reference ref="49223823"/>
+ <reference ref="722745758"/>
+ <reference ref="705341025"/>
+ <reference ref="1025936716"/>
+ <reference ref="294629803"/>
+ <reference ref="776162233"/>
+ <reference ref="425164168"/>
+ <reference ref="579971712"/>
+ <reference ref="1010469920"/>
+ </array>
+ <reference key="parent" ref="379814623"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">75</int>
+ <reference key="object" ref="1023925487"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">78</int>
+ <reference key="object" ref="49223823"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">72</int>
+ <reference key="object" ref="722745758"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">82</int>
+ <reference key="object" ref="705341025"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">124</int>
+ <reference key="object" ref="1025936716"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="1065607017"/>
+ </array>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">77</int>
+ <reference key="object" ref="294629803"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">73</int>
+ <reference key="object" ref="776162233"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">79</int>
+ <reference key="object" ref="425164168"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">112</int>
+ <reference key="object" ref="579971712"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">74</int>
+ <reference key="object" ref="1010469920"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">125</int>
+ <reference key="object" ref="1065607017"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="759406840"/>
+ </array>
+ <reference key="parent" ref="1025936716"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">126</int>
+ <reference key="object" ref="759406840"/>
+ <reference key="parent" ref="1065607017"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">205</int>
+ <reference key="object" ref="789758025"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="437104165"/>
+ <reference ref="583158037"/>
+ <reference ref="1058277027"/>
+ <reference ref="212016141"/>
+ <reference ref="296257095"/>
+ <reference ref="29853731"/>
+ <reference ref="860595796"/>
+ <reference ref="1040322652"/>
+ <reference ref="790794224"/>
+ <reference ref="892235320"/>
+ <reference ref="972420730"/>
+ <reference ref="676164635"/>
+ <reference ref="507821607"/>
+ <reference ref="288088188"/>
+ <reference ref="82994268"/>
+ </array>
+ <reference key="parent" ref="952259628"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">202</int>
+ <reference key="object" ref="437104165"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">198</int>
+ <reference key="object" ref="583158037"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">207</int>
+ <reference key="object" ref="1058277027"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">214</int>
+ <reference key="object" ref="212016141"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">199</int>
+ <reference key="object" ref="296257095"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">203</int>
+ <reference key="object" ref="29853731"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">197</int>
+ <reference key="object" ref="860595796"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">206</int>
+ <reference key="object" ref="1040322652"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">215</int>
+ <reference key="object" ref="790794224"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">218</int>
+ <reference key="object" ref="892235320"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="963351320"/>
+ </array>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">216</int>
+ <reference key="object" ref="972420730"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="769623530"/>
+ </array>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">200</int>
+ <reference key="object" ref="769623530"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="948374510"/>
+ <reference ref="96193923"/>
+ <reference ref="679648819"/>
+ <reference ref="967646866"/>
+ <reference ref="859480356"/>
+ <reference ref="795346622"/>
+ </array>
+ <reference key="parent" ref="972420730"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">219</int>
+ <reference key="object" ref="948374510"/>
+ <reference key="parent" ref="769623530"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">201</int>
+ <reference key="object" ref="96193923"/>
+ <reference key="parent" ref="769623530"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">204</int>
+ <reference key="object" ref="679648819"/>
+ <reference key="parent" ref="769623530"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">220</int>
+ <reference key="object" ref="963351320"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="270902937"/>
+ <reference ref="88285865"/>
+ <reference ref="159080638"/>
+ <reference ref="326711663"/>
+ <reference ref="447796847"/>
+ <reference ref="738670835"/>
+ </array>
+ <reference key="parent" ref="892235320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">213</int>
+ <reference key="object" ref="270902937"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">210</int>
+ <reference key="object" ref="88285865"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">221</int>
+ <reference key="object" ref="159080638"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">208</int>
+ <reference key="object" ref="326711663"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">209</int>
+ <reference key="object" ref="447796847"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">57</int>
+ <reference key="object" ref="110575045"/>
+ <array class="NSMutableArray" key="children">
+ <reference ref="238522557"/>
+ <reference ref="755159360"/>
+ <reference ref="908899353"/>
+ <reference ref="632727374"/>
+ <reference ref="646227648"/>
+ <reference ref="609285721"/>
+ <reference ref="481834944"/>
+ <reference ref="304266470"/>
+ <reference ref="1046388886"/>
+ <reference ref="1056857174"/>
+ <reference ref="342932134"/>
+ </array>
+ <reference key="parent" ref="694149608"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">58</int>
+ <reference key="object" ref="238522557"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">134</int>
+ <reference key="object" ref="755159360"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">150</int>
+ <reference key="object" ref="908899353"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">136</int>
+ <reference key="object" ref="632727374"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">144</int>
+ <reference key="object" ref="646227648"/>
+ <reference key="parent" ref="110575045"/>
<