Browse files

Initial commit

  • Loading branch information...
1 parent 4de8e2a commit 79d1316fe46e2469002b7846be2c15e4c32a0fe0 @robbiehanson robbiehanson committed Jun 2, 2010
Showing with 47,730 additions and 0 deletions.
  1. +12 −0 Benchmarking/BaseNSLogging.h
  2. +92 −0 Benchmarking/BaseNSLogging.m
  3. +12 −0 Benchmarking/DynamicLogging.h
  4. +101 −0 Benchmarking/DynamicLogging.m
  5. +19 −0 Benchmarking/PerformanceTesting.h
  6. +414 −0 Benchmarking/PerformanceTesting.m
  7. +35 −0 Benchmarking/Results/Benchmark PowerMac.csv
  8. +35 −0 Benchmarking/Results/Benchmark iMac.csv
  9. +35 −0 Benchmarking/Results/Benchmark iPad.csv
  10. +35 −0 Benchmarking/Results/Benchmark iPhone 3GS.csv
  11. +35 −0 Benchmarking/Results/Benchmark iPodTouch3 GCD.csv
  12. BIN Benchmarking/Results/Lumberjack Benchmark (PowerMac).ograph
  13. BIN Benchmarking/Results/Lumberjack Benchmark (iMac).ograph
  14. BIN Benchmarking/Results/Lumberjack Benchmark (iPad).ograph
  15. BIN Benchmarking/Results/Lumberjack Benchmark (iPhone 3GS).ograph
  16. +12 −0 Benchmarking/StaticLogging.h
  17. +91 −0 Benchmarking/StaticLogging.m
  18. +27 −0 Lumberjack/DDConsoleLogger.h
  19. +227 −0 Lumberjack/DDConsoleLogger.m
  20. +266 −0 Lumberjack/DDFileLogger.h
  21. +1,243 −0 Lumberjack/DDFileLogger.m
  22. +389 −0 Lumberjack/DDLog.h
  23. +1,001 −0 Lumberjack/DDLog.m
  24. +30 −0 Xcode/BenchmarkIPhone/BenchmarkIPhone-Info.plist
  25. +307 −0 Xcode/BenchmarkIPhone/BenchmarkIPhone.xcodeproj/project.pbxproj
  26. +151 −0 Xcode/BenchmarkIPhone/BenchmarkIPhoneViewController.xib
  27. +8 −0 Xcode/BenchmarkIPhone/BenchmarkIPhone_Prefix.pch
  28. +16 −0 Xcode/BenchmarkIPhone/Classes/BenchmarkIPhoneAppDelegate.h
  29. +27 −0 Xcode/BenchmarkIPhone/Classes/BenchmarkIPhoneAppDelegate.m
  30. +10 −0 Xcode/BenchmarkIPhone/Classes/BenchmarkIPhoneViewController.h
  31. +56 −0 Xcode/BenchmarkIPhone/Classes/BenchmarkIPhoneViewController.m
  32. +219 −0 Xcode/BenchmarkIPhone/MainWindow.xib
  33. +4 −0 Xcode/BenchmarkIPhone/ReadMe.txt
  34. +17 −0 Xcode/BenchmarkIPhone/main.m
  35. +32 −0 Xcode/BenchmarkMac/BenchmarkMac-Info.plist
  36. +340 −0 Xcode/BenchmarkMac/BenchmarkMac.xcodeproj/project.pbxproj
  37. +10 −0 Xcode/BenchmarkMac/BenchmarkMacAppDelegate.h
  38. +14 −0 Xcode/BenchmarkMac/BenchmarkMacAppDelegate.m
  39. +7 −0 Xcode/BenchmarkMac/BenchmarkMac_Prefix.pch
  40. +2 −0 Xcode/BenchmarkMac/English.lproj/InfoPlist.strings
  41. +4,119 −0 Xcode/BenchmarkMac/English.lproj/MainMenu.xib
  42. +4 −0 Xcode/BenchmarkMac/ReadMe.txt
  43. +14 −0 Xcode/BenchmarkMac/main.m
  44. +32 −0 Xcode/CustomFormatters/CustomFormatters-Info.plist
  45. +314 −0 Xcode/CustomFormatters/CustomFormatters.xcodeproj/project.pbxproj
  46. +10 −0 Xcode/CustomFormatters/CustomFormattersAppDelegate.h
  47. +32 −0 Xcode/CustomFormatters/CustomFormattersAppDelegate.m
  48. +7 −0 Xcode/CustomFormatters/CustomFormatters_Prefix.pch
  49. +2 −0 Xcode/CustomFormatters/English.lproj/InfoPlist.strings
  50. +4,119 −0 Xcode/CustomFormatters/English.lproj/MainMenu.xib
  51. +4 −0 Xcode/CustomFormatters/ReadMe.txt
  52. +7 −0 Xcode/CustomFormatters/TestFormatter.h
  53. +12 −0 Xcode/CustomFormatters/TestFormatter.m
  54. +14 −0 Xcode/CustomFormatters/main.m
  55. +32 −0 Xcode/CustomLogLevels/CustomLogLevels-Info.plist
  56. +310 −0 Xcode/CustomLogLevels/CustomLogLevels.xcodeproj/project.pbxproj
  57. +10 −0 Xcode/CustomLogLevels/CustomLogLevelsAppDelegate.h
  58. +26 −0 Xcode/CustomLogLevels/CustomLogLevelsAppDelegate.m
  59. +7 −0 Xcode/CustomLogLevels/CustomLogLevels_Prefix.pch
  60. +2 −0 Xcode/CustomLogLevels/English.lproj/InfoPlist.strings
  61. +4,119 −0 Xcode/CustomLogLevels/English.lproj/MainMenu.xib
  62. +77 −0 Xcode/CustomLogLevels/MYLog.h
  63. +11 −0 Xcode/CustomLogLevels/ReadMe.txt
  64. +14 −0 Xcode/CustomLogLevels/main.m
  65. +2 −0 Xcode/FineGrainedLogging/English.lproj/InfoPlist.strings
  66. +4,119 −0 Xcode/FineGrainedLogging/English.lproj/MainMenu.xib
  67. +32 −0 Xcode/FineGrainedLogging/FineGrainedLogging-Info.plist
  68. +322 −0 Xcode/FineGrainedLogging/FineGrainedLogging.xcodeproj/project.pbxproj
  69. +17 −0 Xcode/FineGrainedLogging/FineGrainedLoggingAppDelegate.h
  70. +25 −0 Xcode/FineGrainedLogging/FineGrainedLoggingAppDelegate.m
  71. +7 −0 Xcode/FineGrainedLogging/FineGrainedLogging_Prefix.pch
  72. +17 −0 Xcode/FineGrainedLogging/MYLog.h
  73. +18 −0 Xcode/FineGrainedLogging/ReadMe.txt
  74. +10 −0 Xcode/FineGrainedLogging/TimerOne.h
  75. +54 −0 Xcode/FineGrainedLogging/TimerOne.m
  76. +10 −0 Xcode/FineGrainedLogging/TimerTwo.h
  77. +54 −0 Xcode/FineGrainedLogging/TimerTwo.m
  78. +14 −0 Xcode/FineGrainedLogging/main.m
  79. +11 −0 Xcode/LogFileCompressor/CompressingLogFileManager.h
  80. +508 −0 Xcode/LogFileCompressor/CompressingLogFileManager.m
  81. +2 −0 Xcode/LogFileCompressor/English.lproj/InfoPlist.strings
  82. +4,119 −0 Xcode/LogFileCompressor/English.lproj/MainMenu.xib
  83. +32 −0 Xcode/LogFileCompressor/LogFileCompressor-Info.plist
  84. +326 −0 Xcode/LogFileCompressor/LogFileCompressor.xcodeproj/project.pbxproj
  85. +15 −0 Xcode/LogFileCompressor/LogFileCompressorAppDelegate.h
  86. +46 −0 Xcode/LogFileCompressor/LogFileCompressorAppDelegate.m
  87. +7 −0 Xcode/LogFileCompressor/LogFileCompressor_Prefix.pch
  88. +4 −0 Xcode/LogFileCompressor/ReadMe.txt
  89. +14 −0 Xcode/LogFileCompressor/main.m
  90. +2 −0 Xcode/OverflowTestMac/English.lproj/InfoPlist.strings
  91. +4,119 −0 Xcode/OverflowTestMac/English.lproj/MainMenu.xib
  92. +32 −0 Xcode/OverflowTestMac/OverflowTestMac-Info.plist
  93. +314 −0 Xcode/OverflowTestMac/OverflowTestMac.xcodeproj/project.pbxproj
  94. +17 −0 Xcode/OverflowTestMac/OverflowTestMacAppDelegate.h
  95. +67 −0 Xcode/OverflowTestMac/OverflowTestMacAppDelegate.m
  96. +7 −0 Xcode/OverflowTestMac/OverflowTestMac_Prefix.pch
  97. +7 −0 Xcode/OverflowTestMac/ReadMe.txt
  98. +8 −0 Xcode/OverflowTestMac/SlowLogger.h
  99. +21 −0 Xcode/OverflowTestMac/SlowLogger.m
  100. +14 −0 Xcode/OverflowTestMac/main.m
  101. +2 −0 Xcode/RollingTestMac/English.lproj/InfoPlist.strings
  102. +4,119 −0 Xcode/RollingTestMac/English.lproj/MainMenu.xib
  103. +1 −0 Xcode/RollingTestMac/ReadMe.txt
  104. +32 −0 Xcode/RollingTestMac/RollingTestMac-Info.plist
  105. +308 −0 Xcode/RollingTestMac/RollingTestMac.xcodeproj/project.pbxproj
  106. +15 −0 Xcode/RollingTestMac/RollingTestMacAppDelegate.h
  107. +46 −0 Xcode/RollingTestMac/RollingTestMacAppDelegate.m
  108. +7 −0 Xcode/RollingTestMac/RollingTestMac_Prefix.pch
  109. +14 −0 Xcode/RollingTestMac/main.m
  110. +10 −0 Xcode/WebServerIPhone/Classes/MyHTTPConnection.h
  111. +256 −0 Xcode/WebServerIPhone/Classes/MyHTTPConnection.m
  112. +14 −0 Xcode/WebServerIPhone/Classes/MyHTTPServer.h
  113. +53 −0 Xcode/WebServerIPhone/Classes/MyHTTPServer.m
  114. +24 −0 Xcode/WebServerIPhone/Classes/WebServerIPhoneAppDelegate.h
  115. +131 −0 Xcode/WebServerIPhone/Classes/WebServerIPhoneAppDelegate.m
  116. +16 −0 Xcode/WebServerIPhone/Classes/WebServerIPhoneViewController.h
  117. +65 −0 Xcode/WebServerIPhone/Classes/WebServerIPhoneViewController.m
  118. +23 −0 Xcode/WebServerIPhone/Classes/WebSocketLogger.h
  119. +145 −0 Xcode/WebServerIPhone/Classes/WebSocketLogger.m
  120. +219 −0 Xcode/WebServerIPhone/MainWindow.xib
  121. +11 −0 Xcode/WebServerIPhone/ReadMe.txt
  122. +515 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/AsyncSocket.h
  123. +3,609 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/AsyncSocket.m
  124. +14 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/DDData.h
  125. +203 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/DDData.m
  126. +12 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/DDNumber.h
  127. +88 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/DDNumber.m
  128. +56 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/DDRange.h
  129. +104 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/DDRange.m
  130. +33 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPAsyncFileResponse.h
  131. +184 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPAsyncFileResponse.m
  132. +43 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPAuthenticationRequest.h
  133. +208 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPAuthenticationRequest.m
  134. +77 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPConnection.h
  135. +2,019 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPConnection.m
  136. +69 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPDynamicFileResponse.h
  137. +340 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPDynamicFileResponse.m
  138. +120 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPResponse.h
  139. +120 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPResponse.m
  140. +65 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPServer.h
  141. +350 −0 Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPServer.m
  142. +13 −0 Xcode/WebServerIPhone/Web/index.html
  143. +154 −0 Xcode/WebServerIPhone/Web/jquery-1.4.2.min.js
  144. +146 −0 Xcode/WebServerIPhone/Web/socket.html
  145. +40 −0 Xcode/WebServerIPhone/Web/styles.css
  146. +30 −0 Xcode/WebServerIPhone/WebServerIPhone-Info.plist
  147. +409 −0 Xcode/WebServerIPhone/WebServerIPhone.xcodeproj/project.pbxproj
  148. +151 −0 Xcode/WebServerIPhone/WebServerIPhoneViewController.xib
  149. +8 −0 Xcode/WebServerIPhone/WebServerIPhone_Prefix.pch
  150. +17 −0 Xcode/WebServerIPhone/main.m
View
12 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
View
92 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
View
12 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
View
101 Benchmarking/DynamicLogging.m
@@ -0,0 +1,101 @@
+#import "DynamicLogging.h"
+#import "PerformanceTesting.h"
+#import "DDLog.h"
+
+#define FILENAME @"DynamicLogging"
+
+// Debug levels: off, error, warn, info, verbose
+static int ddLogLevel = LOG_LEVEL_WARN; // NOT CONST
+
+
+@implementation DynamicLogging
+
++ (int)ddLogLevel
+{
+ return ddLogLevel;
+}
+
++ (void)ddSetLogLevel:(int)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
View
19 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
View
414 Benchmarking/PerformanceTesting.m
@@ -0,0 +1,414 @@
+#import "PerformanceTesting.h"
+#import "DDLog.h"
+#import "DDConsoleLogger.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;
+
++ (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:[DDConsoleLogger sharedInstance]];
+
+ [DDLog flushLog];
+}
+
+/**
+ * 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]];
+
+ [DDLog flushLog];
+}
+
+/**
+ * Suite 3 - Logging to Console & File.
+**/
++ (void)configureLoggingForSuite3
+{
+ [DDLog removeAllLoggers];
+
+ [DDLog addLogger:[DDConsoleLogger sharedInstance]];
+ [DDLog addLogger:[self fileLogger]];
+
+ [DDLog flushLog];
+}
+
++ (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++)
+ {
+ SEL selector;
+ switch (j)
+ {
+ case 0 : selector = @selector(speedTest0); break;
+ case 1 : selector = @selector(speedTest1); break;
+ case 2 : selector = @selector(speedTest2); break;
+ case 3 : selector = @selector(speedTest3); break;
+ default : selector = @selector(speedTest4); break;
+ }
+
+ NSTimeInterval min = DBL_MAX;
+ NSTimeInterval max = DBL_MIN;
+
+ NSTimeInterval total = 0.0;
+
+ for (k = 0; k < NUMBER_OF_RUNS; k++)
+ {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ NSDate *start = [NSDate date];
+
+ [class performSelector:selector];
+
+ NSTimeInterval result = [start timeIntervalSinceNow] * -1.0;
+
+ min = MIN(min, result);
+ max = MAX(max, result);
+
+ total += result;
+
+ [pool release];
+ [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.\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.\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 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 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 runSuite1 = YES;
+ BOOL runSuite2 = YES;
+ BOOL runSuite3 = YES;
+
+ if (!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];
+
+ [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 (runSuite1 && runSuite2 && runSuite3)
+ {
+ #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
View
35 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
View
35 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
View
35 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
View
35 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
View
35 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
View
BIN Benchmarking/Results/Lumberjack Benchmark (PowerMac).ograph
Binary file not shown.
View
BIN Benchmarking/Results/Lumberjack Benchmark (iMac).ograph
Binary file not shown.
View
BIN Benchmarking/Results/Lumberjack Benchmark (iPad).ograph
Binary file not shown.
View
BIN Benchmarking/Results/Lumberjack Benchmark (iPhone 3GS).ograph
Binary file not shown.
View
12 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
View
91 Benchmarking/StaticLogging.m
@@ -0,0 +1,91 @@
+#import "StaticLogging.h"
+#import "PerformanceTesting.h"
+#import "DDLog.h"
+
+#define FILENAME @"StaticLogging " // Trailing space to match exactly the others in length
+
+// Debug levels: off, error, warn, info, verbose
+static const int ddLogLevel = LOG_LEVEL_WARN; // 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
View
27 Lumberjack/DDConsoleLogger.h
@@ -0,0 +1,27 @@
+#import <Foundation/Foundation.h>
+#import <unistd.h>
+#import <asl.h>
+
+#import "DDLog.h"
+
+
+@interface DDConsoleLogger : NSObject <DDLogger>
+{
+ aslclient client;
+
+ BOOL isRunningInXcode;
+
+ NSDateFormatter *dateFormatter;
+
+ char *app; // Not null terminated
+ char *pid; // Not null terminated
+
+ int appLen;
+ int pidLen;
+
+ id <DDLogFormatter> formatter;
+}
+
++ (DDConsoleLogger *)sharedInstance;
+
+@end
View
227 Lumberjack/DDConsoleLogger.m
@@ -0,0 +1,227 @@
+#import "DDConsoleLogger.h"
+#import <sys/uio.h>
+
+
+@implementation DDConsoleLogger
+
+static DDConsoleLogger *sharedInstance;
+
+/**
+ * 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 BOOL initialized = NO;
+ if (!initialized)
+ {
+ initialized = YES;
+
+ sharedInstance = [[DDConsoleLogger alloc] init];
+ }
+}
+
++ (DDConsoleLogger *)sharedInstance
+{
+ return sharedInstance;
+}
+
+- (id)init
+{
+ if (sharedInstance != nil)
+ {
+ [self release];
+ return nil;
+ }
+
+ if ((self = [super init]))
+ {
+ // To log to the console, we use the apple system logging facility.
+ // 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);
+
+ // If we are running the application via Xcode,
+ // we want our log messages to have the same look and feel and normal NSLog statements.
+
+ isRunningInXcode = isatty(STDERR_FILENO);
+
+ if (isRunningInXcode)
+ {
+ dateFormatter = [[NSDateFormatter alloc] init];
+ [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
+ [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss:SSS"];
+
+ // Initialze 'app' variable (char *)
+
+ NSString *appNStr = [[NSProcessInfo processInfo] processName];
+ const char *appCStr = [appNStr UTF8String];
+
+ appLen = strlen(appCStr);
+
+ app = (char *)malloc(appLen);
+ strncpy(app, appCStr, appLen); // Not null terminated
+
+ // Initialize 'pid' variable (char *)
+
+ NSString *pidNStr = [NSString stringWithFormat:@"%i", (int)getpid()];
+ const char *pidCStr = [pidNStr UTF8String];
+
+ pidLen = strlen(pidCStr);
+
+ pid = (char *)malloc(pidLen);
+ strncpy(pid, pidCStr, pidLen); // Not null terminated
+ }
+ }
+ return self;
+}
+
+- (void)logMessage:(DDLogMessage *)logMessage
+{
+ NSString *logMsg = logMessage->logMsg;
+
+ if (formatter)
+ {
+ logMsg = [formatter formatLogMessage:logMessage];
+ }
+
+ if (logMsg)
+ {
+ // Can we simply use NSLog? Yes and no.
+ // An NSLog message looks like this in Xcode:
+ //
+ // 2010-04-29 11:09:05.800 AppName[34209:a0f] Some log message...
+ //
+ // The thing to notice is the [<process id>:<thread id>] part.
+ // Any NSLog statements from this method would have the thread id of the logging thread.
+ // It would not have the proper thread id of the thread that actually issued the DDLog statement.
+ //
+ // To solve this problem we have to know a little bit about how NSLog actually works internally.
+ // There are three important things to note:
+ //
+ // - NSLog uses the Apple System Logging (asl) library to make log messages show up in Console.app.
+ // - The Xcode console gets its output by redirecting STDERR.
+ // - NSLog issues a separate log statement for Xcode console output.
+ //
+ // One can also look at Apple's open source CFUtilities.c file to observe how CFShow works,
+ // which does the equivalent of NSLog.
+ //
+ // One other quick thing to mention:
+ //
+ // It is possible to open an asl_client in such a manner that it also writes to STDERR.
+ // And when you issue a log statement through such an asl client,
+ // the message shows up in Console.app and also in the Xcode console.
+ // However, the log message doesn't look anything like NSLog.
+ // Instead it looks something like this:
+ //
+ // Thu Apr 29 11:09:05 ComputerName.local AppName[34209] <Notice>: Some log message...
+ //
+ // So we duplicate NSLog in the same manner that CFShow works (and NSLog by extension).
+
+
+ // Log the message to the Console.app
+
+ const char *msg = [logMsg UTF8String];
+
+ int aslLogLevel;
+ switch (logMessage->logLevel)
+ {
+ // Note: By default ASL will filter anything above level 5 (Notice).
+ // So our mappings shouldn't go above that level.
+
+ case 1 : aslLogLevel = ASL_LEVEL_CRIT; break;
+ case 2 : aslLogLevel = ASL_LEVEL_ERR; break;
+ case 3 : aslLogLevel = ASL_LEVEL_WARNING; break;
+ default : aslLogLevel = ASL_LEVEL_NOTICE; break;
+ }
+
+ asl_log(client, NULL, aslLogLevel, "%s", msg);
+
+ // Log the message to the Xcode console (if running via Xcode)
+
+ if (isRunningInXcode)
+ {
+ // The following is a highly optimized verion of file output to std err.
+
+ // ts = timestamp
+
+ NSString *tsNStr = [dateFormatter stringFromDate:(logMessage->timestamp)];
+
+ const char *tsCStr = [tsNStr UTF8String];
+ int tsLen = strlen(tsCStr);
+
+ // tid = 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 tidCStr[9];
+ int tidLen = snprintf(tidCStr, 9, "%x", logMessage->machThreadID);
+
+ // Here is our format: "%s %s[%i:%s] %s", timestamp, appName, processID, threadID, logMsg
+
+ struct iovec v[10];
+
+ v[0].iov_base = (char *)tsCStr;
+ v[0].iov_len = tsLen;
+
+ v[1].iov_base = " ";
+ v[1].iov_len = 1;
+
+ v[2].iov_base = app;
+ v[2].iov_len = appLen;
+
+ v[3].iov_base = "[";
+ v[3].iov_len = 1;
+
+ v[4].iov_base = pid;
+ v[4].iov_len = pidLen;
+
+ v[5].iov_base = ":";
+ v[5].iov_len = 1;
+
+ v[6].iov_base = tidCStr;
+ v[6].iov_len = MIN(8, tidLen); // snprintf doesn't return what you might think
+
+ v[7].iov_base = "] ";
+ v[7].iov_len = 2;
+
+ v[8].iov_base = (char *)msg;
+ v[8].iov_len = strlen(msg);
+
+ v[9].iov_base = "\n";
+ v[9].iov_len = [logMsg hasSuffix:@"\n"] ? 0 : 1;
+
+ writev(STDERR_FILENO, v, 10);
+ }
+ }
+}
+
+- (id <DDLogFormatter>)logFormatter
+{
+ return formatter;
+}
+
+- (void)setLogFormatter:(id <DDLogFormatter>)logFormatter
+{
+ if (formatter != logFormatter)
+ {
+ [formatter release];
+ formatter = [logFormatter retain];
+ }
+}
+
+- (NSString *)loggerName
+{
+ return @"cocoa.lumberjack.consoleLogger";
+}
+
+@end
View
266 Lumberjack/DDFileLogger.h
@@ -0,0 +1,266 @@
+#import <Foundation/Foundation.h>
+#import "DDLog.h"
+
+@class DDLogFileInfo;
+
+
+// Default configuration and safety/sanity values.
+//
+// maximumFileSize -> DEFAULT_LOG_MAX_FILE_SIZE
+// rollingFrequency -> DEFAULT_LOG_ROLLING_FREQUENCY
+// maximumNumberOfLogFiles -> DEFAULT_LOG_MAX_NUM_LOG_FILES
+//
+// You should carefully consider the proper configuration values for your application.
+
+#define DEFAULT_LOG_MAX_FILE_SIZE (1024 * 1024) // 1 MB
+#define DEFAULT_LOG_ROLLING_FREQUENCY (60 * 60 * 24) // 24 Hours
+#define DEFAULT_LOG_MAX_NUM_LOG_FILES (5) // 5 Files
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#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
+
+@property (readwrite, assign) NSUInteger maximumNumberOfLogFiles;
+
+// Public methods
+
+- (NSString *)logsDirectory;
+
+- (NSArray *)unsortedLogFilePaths;
+- (NSArray *)unsortedLogFileNames;
+- (NSArray *)unsortedLogFileInfos;
+
+- (NSArray *)sortedLogFilePaths;
+- (NSArray *)sortedLogFileNames;
+- (NSArray *)sortedLogFileInfos;
+
+// Private methods (only to be used by DDFileLogger)
+
+- (NSString *)createNewLogFile;
+
+@optional
+
+// Notifications from DDFileLogger
+
+- (void)didArchiveLogFile:(NSString *)logFilePath;
+- (void)didRollAndArchiveLogFile:(NSString *)logFilePath;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Default log file manager.
+//
+// All log files are placed inside the logsDirectory.
+// On Mac, this is in ~/Library/Application Support/<Application Name>/Logs.
+// On iPhone, this is in ~/Documents/Logs.
+//
+// Log files are named "log-<uuid>.txt",
+// where uuid is a 6 character hexadecimal consisting of the set [0123456789ABCDEF].
+//
+// Archived log files are automatically deleted according to the maximumNumberOfLogFiles property.
+
+@interface DDLogFileManagerDefault : NSObject <DDLogFileManager>
+{
+ NSUInteger maximumNumberOfLogFiles;
+}
+
+@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>
+{
+ NSDateFormatter *dateFormatter;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface DDFileLogger : NSObject <DDLogger>
+{
+ id <DDLogFormatter> formatter;
+ id <DDLogFileManager> logFileManager;
+
+ DDLogFileInfo *currentLogFileInfo;
+ NSFileHandle *currentLogFileHandle;
+
+ NSTimer *rollingTimer;
+
+ unsigned long long maximumFileSize;
+ NSTimeInterval rollingFrequency;
+}
+
+- (id)init;
+- (id)initWithLogFileManager:(id <DDLogFileManager>)logFileManager;
+
+// Configuration
+//
+// maximumFileSize:
+// The approximate maximum size to allow log files to grow.
+// If a log file is larger than this value after a write,
+// 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.
+//
+// logFileManager
+// Allows you to retrieve the list of log files,
+// and configure the maximum number of archived log files to keep.
+
+@property (readwrite, assign) unsigned long long maximumFileSize;
+
+@property (readwrite, assign) NSTimeInterval rollingFrequency;
+
+@property (nonatomic, readonly) id <DDLogFileManager> logFileManager;
+
+@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
+{
+ NSString *filePath;
+ NSString *fileName;
+
+ NSDictionary *fileAttributes;
+
+ NSDate *creationDate;
+ NSDate *modificationDate;
+
+ unsigned long long fileSize;
+}
+
+@property (nonatomic, readonly) NSString *filePath;
+@property (nonatomic, readonly) NSString *fileName;
+
+@property (nonatomic, readonly) NSDictionary *fileAttributes;
+
+@property (nonatomic, readonly) NSDate *creationDate;
+@property (nonatomic, readonly) NSDate *modificationDate;
+
+@property (nonatomic, readonly) unsigned long long fileSize;
+
+@property (nonatomic, readonly) NSTimeInterval age;
+
+@property (nonatomic, readwrite) BOOL isArchived;
+
++ (id)logFileWithPath:(NSString *)filePath;
+
+- (id)initWithFilePath:(NSString *)filePath;
+
+- (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:
+// log-ABC123.txt -> log-ABC123.archived.txt
+
+- (BOOL)hasExtensionAttributeWithName:(NSString *)attrName;
+
+- (void)addExtensionAttributeWithName:(NSString *)attrName;
+- (void)removeExtensionAttributeWithName:(NSString *)attrName;
+
+#else
+
+// 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
+
+- (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another;
+- (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another;
+
+@end
View
1,243 Lumberjack/DDFileLogger.m
@@ -0,0 +1,1243 @@
+#import "DDFileLogger.h"
+
+#import <sys/attr.h>
+#import <sys/xattr.h>
+
+// 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.
+
+#define LOG_LEVEL 4
+
+#define NSLogError(frmt, ...) do{ if(LOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogWarn(frmt, ...) do{ if(LOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogInfo(frmt, ...) do{ if(LOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogVerbose(frmt, ...) do{ if(LOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0)
+
+@interface DDLogFileManagerDefault (PrivateAPI)
+- (void)deleteOldLogFiles;
+@end
+
+@interface DDFileLogger (PrivateAPI)
+- (void)maybeRollLogFileDueToAge:(NSTimer *)aTimer;
+- (void)maybeRollLogFileDueToSize;
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation DDLogFileManagerDefault
+
+@synthesize maximumNumberOfLogFiles;
+
+
+- (id)init
+{
+ if ((self = [super init]))
+ {
+ maximumNumberOfLogFiles = DEFAULT_LOG_MAX_NUM_LOG_FILES;
+
+ NSKeyValueObservingOptions kvoOptions = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
+
+ [self addObserver:self forKeyPath:@"maximumNumberOfLogFiles" options:kvoOptions context:nil];
+
+ NSLogVerbose(@"DDFileLogManagerDefault: logsDir:\n%@", [self logsDirectory]);
+ NSLogVerbose(@"DDFileLogManagerDefault: sortedLogFileNames:\n%@", [self sortedLogFileNames]);
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [super dealloc];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context
+{
+ NSNumber *old = [change objectForKey:NSKeyValueChangeOldKey];
+ NSNumber *new = [change objectForKey:NSKeyValueChangeNewKey];
+
+ if ([old isEqual:new])
+ {
+ // No change in value - don't bother with any processing.
+ return;
+ }
+
+ if ([keyPath isEqualToString:@"maximumNumberOfLogFiles"])
+ {
+ NSLogInfo(@"DDFileLogManagerDefault: Responding to configuration change: maximumNumberOfLogFiles");
+
+ #if GCD_AVAILABLE
+
+ dispatch_block_t block = ^{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ [self deleteOldLogFiles];
+
+ [pool release];
+ };
+
+ dispatch_async([DDLog loggingQueue], block);
+
+ #else
+
+ [self performSelector:@selector(deleteOldLogFiles)
+ onThread:[DDLog loggingThread]
+ withObject:nil
+ waitUntilDone:NO];
+
+ #endif
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark File Deleting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Deletes archived log files that exceed the maximumNumberOfLogFiles configuration value.
+**/
+- (void)deleteOldLogFiles
+{
+ NSLogVerbose(@"DDLogFileManagerDefault: deleteOldLogFiles");
+
+ NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
+
+ NSUInteger maxNumLogFiles = self.maximumNumberOfLogFiles;
+
+ // 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.
+
+ NSUInteger count = [sortedLogFileInfos count];
+ BOOL excludeFirstFile = NO;
+
+ if (count > 0)
+ {
+ DDLogFileInfo *logFileInfo = [sortedLogFileInfos objectAtIndex:0];
+
+ if (!logFileInfo.isArchived)
+ {
+ excludeFirstFile = YES;
+ }
+ }
+
+ NSArray *sortedArchivedLogFileInfos;
+ if (excludeFirstFile)
+ {
+ count--;
+ sortedArchivedLogFileInfos = [sortedLogFileInfos subarrayWithRange:NSMakeRange(1, count)];
+ }
+ else
+ {
+ sortedArchivedLogFileInfos = sortedLogFileInfos;
+ }
+
+ for (NSUInteger i = 0; i < count; i++)
+ {
+ if (i >= maxNumLogFiles)
+ {
+ DDLogFileInfo *logFileInfo = [sortedArchivedLogFileInfos objectAtIndex:i];
+
+ NSLogInfo(@"DDLogFileManagerDefault: Deleting file: %@", logFileInfo.fileName);
+
+ [[NSFileManager defaultManager] removeItemAtPath:logFileInfo.filePath error:nil];
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Log Files
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns the path to the logs directory.
+ * If the logs directory doesn't exist, this method automatically creates it.
+**/
+- (NSString *)logsDirectory
+{
+#if TARGET_OS_IPHONE
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+ NSString *baseDir = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
+#else
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
+ NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory();
+
+ NSString *appName = [[NSProcessInfo processInfo] processName];
+
+ NSString *baseDir = [basePath stringByAppendingPathComponent:appName];
+#endif
+
+ NSString *logsDir = [baseDir stringByAppendingPathComponent:@"Logs"];
+
+ if(![[NSFileManager defaultManager] fileExistsAtPath:logsDir])
+ {
+ NSError *err = nil;
+ if(![[NSFileManager defaultManager] createDirectoryAtPath:logsDir
+ withIntermediateDirectories:YES attributes:nil error:&err])
+ {
+ NSLogError(@"DDFileLogManagerDefault: Error creating logsDirectory: %@", err);
+ }
+ }
+
+ return logsDir;
+}
+
+- (BOOL)isLogFile:(NSString *)fileName
+{
+ // A log file has a name like "log-<uuid>.txt", where <uuid> is a HEX-string of 6 characters.
+ //
+ // For example: log-DFFE99.txt
+
+ BOOL hasProperPrefix = [fileName hasPrefix:@"log-"];
+
+ BOOL hasProperLength = [fileName length] >= 10;
+
+
+ if (hasProperPrefix && hasProperLength)
+ {
+ NSCharacterSet *hexSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789ABCDEF"];
+
+ NSString *hex = [fileName substringWithRange:NSMakeRange(4, 6)];
+ NSString *nohex = [hex stringByTrimmingCharactersInSet:hexSet];
+
+ if ([nohex length] == 0)
+ {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
+/**
+ * Returns an array of NSString objects,
+ * each of which is the filePath to an existing log file on disk.
+**/
+- (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 ([self isLogFile:fileName])
+ {
+ NSString *filePath = [logsDirectory stringByAppendingPathComponent:fileName];
+
+ [unsortedLogFilePaths addObject:filePath];
+ }
+ }
+
+ return unsortedLogFilePaths;
+}
+
+/**
+ * Returns an array of NSString objects,
+ * each of which is the fileName of an existing log file on disk.
+**/
+- (NSArray *)unsortedLogFileNames
+{
+ NSArray *unsortedLogFilePaths = [self unsortedLogFilePaths];
+
+ NSMutableArray *unsortedLogFileNames = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]];
+
+ for (NSString *filePath in unsortedLogFilePaths)
+ {
+ [unsortedLogFileNames addObject:[filePath lastPathComponent]];
+ }
+
+ return 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
+{
+ NSArray *unsortedLogFilePaths = [self unsortedLogFilePaths];
+
+ NSMutableArray *unsortedLogFileInfos = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]];
+
+ for (NSString *filePath in unsortedLogFilePaths)
+ {
+ DDLogFileInfo *logFileInfo = [[DDLogFileInfo alloc] initWithFilePath:filePath];
+
+ [unsortedLogFileInfos addObject:logFileInfo];
+ [logFileInfo release];
+ }
+
+ return unsortedLogFileInfos;
+}
+
+/**
+ * Just like the unsortedLogFilePaths method, but sorts the array.
+ * The items in the array are sorted by modification date.
+ * The first item in the array will be the most recently modified log file.
+**/
+- (NSArray *)sortedLogFilePaths
+{
+ NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
+
+ NSMutableArray *sortedLogFilePaths = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]];
+
+ for (DDLogFileInfo *logFileInfo in sortedLogFileInfos)
+ {
+ [sortedLogFilePaths addObject:[logFileInfo filePath]];
+ }
+
+ return sortedLogFilePaths;
+}
+
+/**
+ * Just like the unsortedLogFileNames method, but sorts the array.
+ * The items in the array are sorted by modification date.
+ * The first item in the array will be the most recently modified log file.
+**/
+- (NSArray *)sortedLogFileNames
+{
+ NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
+
+ NSMutableArray *sortedLogFileNames = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]];
+
+ for (DDLogFileInfo *logFileInfo in sortedLogFileInfos)
+ {
+ [sortedLogFileNames addObject:[logFileInfo fileName]];
+ }
+
+ return sortedLogFileNames;
+}
+
+/**
+ * Just like the unsortedLogFileInfos method, but sorts the array.
+ * The items in the array are sorted by modification date.
+ * The first item in the array will be the most recently modified log file.
+**/
+- (NSArray *)sortedLogFileInfos
+{
+ return [[self unsortedLogFileInfos] sortedArrayUsingSelector:@selector(reverseCompareByCreationDate:)];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Creation
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Generates a short UUID suitable for use in the log file's name.
+ * The result will have six characters, all in the hexadecimal set [0123456789ABCDEF].
+**/
+- (NSString *)generateShortUUID
+{
+ CFUUIDRef uuid = CFUUIDCreate(NULL);
+
+ CFStringRef fullStr = CFUUIDCreateString(NULL, uuid);
+ CFStringRef shortStr = CFStringCreateWithSubstring(NULL, fullStr, CFRangeMake(0, 6));
+
+ CFRelease(fullStr);
+ CFRelease(uuid);
+
+ return [NSMakeCollectable(shortStr) autorelease];
+}
+
+/**
+ * Generates a new unique log file path, and creates the corresponding log file.
+**/
+- (NSString *)createNewLogFile
+{
+ // Generate a random log file name, and create the file (if there isn't a collision)
+
+ NSString *logsDirectory = [self logsDirectory];
+ do
+ {
+ NSString *fileName = [NSString stringWithFormat:@"log-%@.txt", [self generateShortUUID]];
+
+ NSString *filePath = [logsDirectory stringByAppendingPathComponent:fileName];
+
+ if (![[NSFileManager defaultManager] fileExistsAtPath:filePath])
+ {
+ NSLogVerbose(@"DDLogFileManagerDefault: Creating new log file: %@", fileName);
+
+ [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
+
+ // Since we just created a new log file, we may need to delete some old log files
+ [self deleteOldLogFiles];
+
+ return filePath;
+ }
+
+ } while(YES);
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation DDLogFileFormatterDefault
+
+- (id)init
+{
+ if((self = [super init]))
+ {
+ dateFormatter = [[NSDateFormatter alloc] init];
+ [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
+ [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->logMsg];
+}
+
+- (void)dealloc
+{
+ [dateFormatter release];
+ [super dealloc];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation DDFileLogger
+
+@synthesize maximumFileSize;
+@synthesize rollingFrequency;
+@synthesize logFileManager;
+
+- (id)init
+{
+ DDLogFileManagerDefault *defaultLogFileManager = [[[DDLogFileManagerDefault alloc] init] autorelease];
+
+ return [self initWithLogFileManager:defaultLogFileManager];
+}
+
+- (id)initWithLogFileManager:(id <DDLogFileManager>)aLogFileManager
+{
+ if ((self = [super init]))
+ {
+ maximumFileSize = DEFAULT_LOG_MAX_FILE_SIZE;
+ rollingFrequency = DEFAULT_LOG_ROLLING_FREQUENCY;
+
+ logFileManager = [aLogFileManager retain];
+
+ formatter = [[DDLogFileFormatterDefault alloc] init];
+
+ NSKeyValueObservingOptions kvoOptions = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
+
+ [self addObserver:self forKeyPath:@"maximumFileSize" options:kvoOptions context:nil];
+ [self addObserver:self forKeyPath:@"rollingFrequency" options:kvoOptions context:nil];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [formatter release];
+ [logFileManager release];
+
+ [currentLogFileInfo release];
+
+ [currentLogFileHandle synchronizeFile];
+ [currentLogFileHandle closeFile];
+ [currentLogFileHandle release];
+
+ [rollingTimer invalidate];
+ [rollingTimer release];
+
+ [super dealloc];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context
+{
+ NSNumber *old = [change objectForKey:NSKeyValueChangeOldKey];
+ NSNumber *new = [change objectForKey:NSKeyValueChangeNewKey];
+
+ if ([old isEqual:new])
+ {
+ // No change in value - don't bother with any processing.
+ return;
+ }
+
+ if ([keyPath isEqualToString:@"maximumFileSize"])
+ {
+ NSLogInfo(@"DDFileLogger: Responding to configuration change: maximumFileSize");
+
+ #if GCD_AVAILABLE
+
+ dispatch_block_t block = ^{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ [self maybeRollLogFileDueToSize];
+
+ [pool release];
+ };
+
+ dispatch_async([DDLog loggingQueue], block);
+ #else
+
+ [self performSelector:@selector(maybeRollLogFileDueToSize)
+ onThread:[DDLog loggingThread]
+ withObject:nil
+ waitUntilDone:NO];
+
+ #endif
+ }
+ else if([keyPath isEqualToString:@"rollingFrequency"])
+ {
+ NSLogInfo(@"DDFileLogger: Responding to configuration change: rollingFrequency");
+
+ #if GCD_AVAILABLE
+
+ dispatch_block_t block = ^{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ [self maybeRollLogFileDueToAge:nil];
+
+ [pool release];
+ };
+
+ dispatch_async([DDLog loggingQueue], block);
+
+ #else
+
+ [self performSelector:@selector(maybeRollLogFileDueToAge:)
+ onThread:[DDLog loggingThread]
+ withObject:nil
+ waitUntilDone:NO];
+
+ #endif
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark File Rolling
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)scheduleTimerToRollLogFileDueToAge
+{
+ if (rollingTimer)
+ {
+ [rollingTimer invalidate];
+ [rollingTimer release];
+ rollingTimer = nil;
+ }
+
+ if (currentLogFileInfo == nil)
+ {
+ return;
+ }
+
+ NSDate *logFileCreationDate = [currentLogFileInfo creationDate];
+
+ NSTimeInterval ti = [logFileCreationDate timeIntervalSinceReferenceDate];
+ ti += self.rollingFrequency;
+
+ NSDate *logFileRollingDate = [NSDate dateWithTimeIntervalSinceReferenceDate:ti];
+
+ NSLogVerbose(@"DDFileLogger: scheduleTimerToRollLogFileDueToAge");
+
+ NSLogVerbose(@"DDFileLogger: logFileCreationDate: %@", logFileCreationDate);
+ NSLogVerbose(@"DDFileLogger: logFileRollingDate : %@", logFileRollingDate);
+
+ rollingTimer = [[NSTimer scheduledTimerWithTimeInterval:[logFileRollingDate timeIntervalSinceNow]
+ target:self
+ selector:@selector(maybeRollLogFileDueToAge:)
+ userInfo:nil
+ repeats:NO] retain];
+}
+
+- (void)rollLogFile
+{
+ NSLogVerbose(@"DDFileLogger: rollLogFile");
+
+ [currentLogFileHandle synchronizeFile];
+ [currentLogFileHandle closeFile];
+ [currentLogFileHandle release];
+ currentLogFileHandle = nil;
+
+ currentLogFileInfo.isArchived = YES;
+
+ if ([logFileManager respondsToSelector:@selector(didRollAndArchiveLogFile:)])
+ {
+ [logFileManager didRollAndArchiveLogFile:(currentLogFileInfo.filePath)];
+ }
+
+ [currentLogFileInfo release];
+ currentLogFileInfo = nil;
+}
+
+- (void)maybeRollLogFileDueToAge:(NSTimer *)aTimer
+{
+ if (currentLogFileInfo.age >= self.rollingFrequency)
+ {
+ NSLogVerbose(@"DDFileLogger: Rolling log file due to age...");
+
+ [self rollLogFile];
+ }
+ else
+ {
+ [self scheduleTimerToRollLogFileDueToAge];
+ }
+}
+
+- (void)maybeRollLogFileDueToSize
+{
+ unsigned long long fileSize = [currentLogFileHandle offsetInFile];
+
+ if (fileSize >= self.maximumFileSize)
+ {
+ NSLogVerbose(@"DDFileLogger: Rolling log file due to size...");
+
+ [self rollLogFile];
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#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 objectAtIndex:0];
+
+ BOOL useExistingLogFile = YES;
+ BOOL shouldArchiveMostRecent = NO;
+
+ if (mostRecentLogFileInfo.isArchived)
+ {
+ useExistingLogFile = NO;
+ shouldArchiveMostRecent = NO;
+ }
+ else if (mostRecentLogFileInfo.fileSize >= self.maximumFileSize)
+ {
+ useExistingLogFile = NO;
+ shouldArchiveMostRecent = YES;
+ }
+ else if (mostRecentLogFileInfo.age >= self.rollingFrequency)
+ {
+ useExistingLogFile = NO;
+ shouldArchiveMostRecent = YES;
+ }
+
+ if (useExistingLogFile)
+ {
+ NSLogVerbose(@"DDFileLogger: Resuming logging with file %@", mostRecentLogFileInfo.fileName);
+
+ currentLogFileInfo = [mostRecentLogFileInfo retain];
+ }
+ else
+ {
+ if (shouldArchiveMostRecent)
+ {
+ mostRecentLogFileInfo.isArchived = YES;
+
+ if ([logFileManager respondsToSelector:@selector(didArchiveLogFile:)])
+ {
+ [logFileManager didArchiveLogFile:(mostRecentLogFileInfo.filePath)];
+ }
+ }
+ }
+ }