FMDBLogger.m 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. //
  2. // FMDBLogger.m
  3. // SQLiteLogger
  4. //
  5. // CocoaLumberjack Demos
  6. //
  7. #import "FMDBLogger.h"
  8. #import "FMDatabase.h"
  9. @interface FMDBLogger ()
  10. - (void)validateLogDirectory;
  11. - (void)openDatabase;
  12. @end
  13. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  14. #pragma mark -
  15. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  16. @interface FMDBLogEntry : NSObject {
  17. @public
  18. NSNumber * context;
  19. NSNumber * level;
  20. NSString * message;
  21. NSDate * timestamp;
  22. }
  23. - (id)initWithLogMessage:(DDLogMessage *)logMessage;
  24. @end
  25. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  26. #pragma mark -
  27. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  28. @implementation FMDBLogEntry
  29. - (id)initWithLogMessage:(DDLogMessage *)logMessage
  30. {
  31. if ((self = [super init]))
  32. {
  33. context = @(logMessage->_context);
  34. level = @(logMessage->_flag);
  35. message = logMessage->_message;
  36. timestamp = logMessage->_timestamp;
  37. }
  38. return self;
  39. }
  40. @end
  41. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  42. #pragma mark -
  43. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  44. @implementation FMDBLogger
  45. - (id)initWithLogDirectory:(NSString *)aLogDirectory
  46. {
  47. if ((self = [super init]))
  48. {
  49. logDirectory = [aLogDirectory copy];
  50. pendingLogEntries = [[NSMutableArray alloc] initWithCapacity:_saveThreshold];
  51. [self validateLogDirectory];
  52. [self openDatabase];
  53. }
  54. return self;
  55. }
  56. - (void)validateLogDirectory
  57. {
  58. // Validate log directory exists or create the directory.
  59. BOOL isDirectory;
  60. if ([[NSFileManager defaultManager] fileExistsAtPath:logDirectory isDirectory:&isDirectory])
  61. {
  62. if (!isDirectory)
  63. {
  64. NSLog(@"%@: %@ - logDirectory(%@) is a file!", [self class], THIS_METHOD, logDirectory);
  65. logDirectory = nil;
  66. }
  67. }
  68. else
  69. {
  70. NSError *error = nil;
  71. BOOL result = [[NSFileManager defaultManager] createDirectoryAtPath:logDirectory
  72. withIntermediateDirectories:YES
  73. attributes:nil
  74. error:&error];
  75. if (!result)
  76. {
  77. NSLog(@"%@: %@ - Unable to create logDirectory(%@) due to error: %@",
  78. [self class], THIS_METHOD, logDirectory, error);
  79. logDirectory = nil;
  80. }
  81. }
  82. }
  83. - (void)openDatabase
  84. {
  85. if (logDirectory == nil)
  86. {
  87. return;
  88. }
  89. NSString *path = [logDirectory stringByAppendingPathComponent:@"log.sqlite"];
  90. database = [[FMDatabase alloc] initWithPath:path];
  91. if (![database open])
  92. {
  93. NSLog(@"%@: Failed opening database!", [self class]);
  94. database = nil;
  95. return;
  96. }
  97. NSString *cmd1 = @"CREATE TABLE IF NOT EXISTS logs (context integer, "
  98. "level integer, "
  99. "message text, "
  100. "timestamp double)";
  101. [database executeUpdate:cmd1];
  102. if ([database hadError])
  103. {
  104. NSLog(@"%@: Error creating table: code(%d): %@",
  105. [self class], [database lastErrorCode], [database lastErrorMessage]);
  106. database = nil;
  107. }
  108. NSString *cmd2 = @"CREATE INDEX IF NOT EXISTS timestamp ON logs (timestamp)";
  109. [database executeUpdate:cmd2];
  110. if ([database hadError])
  111. {
  112. NSLog(@"%@: Error creating index: code(%d): %@",
  113. [self class], [database lastErrorCode], [database lastErrorMessage]);
  114. database = nil;
  115. }
  116. [database setShouldCacheStatements:YES];
  117. }
  118. #pragma mark AbstractDatabaseLogger Overrides
  119. - (BOOL)db_log:(DDLogMessage *)logMessage
  120. {
  121. // You may be wondering, how come we don't just do the insert here and be done with it?
  122. // Is the buffering really needed?
  123. //
  124. // From the SQLite FAQ:
  125. //
  126. // (19) INSERT is really slow - I can only do few dozen INSERTs per second
  127. //
  128. // Actually, SQLite will easily do 50,000 or more INSERT statements per second on an average desktop computer.
  129. // But it will only do a few dozen transactions per second. Transaction speed is limited by the rotational
  130. // speed of your disk drive. A transaction normally requires two complete rotations of the disk platter, which
  131. // on a 7200RPM disk drive limits you to about 60 transactions per second.
  132. //
  133. // Transaction speed is limited by disk drive speed because (by default) SQLite actually waits until the data
  134. // really is safely stored on the disk surface before the transaction is complete. That way, if you suddenly
  135. // lose power or if your OS crashes, your data is still safe. For details, read about atomic commit in SQLite.
  136. //
  137. // By default, each INSERT statement is its own transaction. But if you surround multiple INSERT statements
  138. // with BEGIN...COMMIT then all the inserts are grouped into a single transaction. The time needed to commit
  139. // the transaction is amortized over all the enclosed insert statements and so the time per insert statement
  140. // is greatly reduced.
  141. FMDBLogEntry *logEntry = [[FMDBLogEntry alloc] initWithLogMessage:logMessage];
  142. [pendingLogEntries addObject:logEntry];
  143. // Return YES if an item was added to the buffer.
  144. // Return NO if the logMessage was ignored.
  145. return YES;
  146. }
  147. - (void)db_save
  148. {
  149. if ([pendingLogEntries count] == 0)
  150. {
  151. // Nothing to save.
  152. // The superclass won't likely call us if this is the case, but we're being cautious.
  153. return;
  154. }
  155. BOOL saveOnlyTransaction = ![database inTransaction];
  156. if (saveOnlyTransaction)
  157. {
  158. [database beginTransaction];
  159. }
  160. NSString *cmd = @"INSERT INTO logs (context, level, message, timestamp) VALUES (?, ?, ?, ?)";
  161. for (FMDBLogEntry *logEntry in pendingLogEntries)
  162. {
  163. [database executeUpdate:cmd, logEntry->context,
  164. logEntry->level,
  165. logEntry->message,
  166. logEntry->timestamp];
  167. }
  168. [pendingLogEntries removeAllObjects];
  169. if (saveOnlyTransaction)
  170. {
  171. [database commit];
  172. if ([database hadError])
  173. {
  174. NSLog(@"%@: Error inserting log entries: code(%d): %@",
  175. [self class], [database lastErrorCode], [database lastErrorMessage]);
  176. }
  177. }
  178. }
  179. - (void)db_delete
  180. {
  181. if (_maxAge <= 0.0)
  182. {
  183. // Deleting old log entries is disabled.
  184. // The superclass won't likely call us if this is the case, but we're being cautious.
  185. return;
  186. }
  187. BOOL deleteOnlyTransaction = ![database inTransaction];
  188. NSDate *maxDate = [NSDate dateWithTimeIntervalSinceNow:(-1.0 * _maxAge)];
  189. [database executeUpdate:@"DELETE FROM logs WHERE timestamp < ?", maxDate];
  190. if (deleteOnlyTransaction)
  191. {
  192. if ([database hadError])
  193. {
  194. NSLog(@"%@: Error deleting log entries: code(%d): %@",
  195. [self class], [database lastErrorCode], [database lastErrorMessage]);
  196. }
  197. }
  198. }
  199. - (void)db_saveAndDelete
  200. {
  201. [database beginTransaction];
  202. [self db_delete];
  203. [self db_save];
  204. [database commit];
  205. if ([database hadError])
  206. {
  207. NSLog(@"%@: Error: code(%d): %@",
  208. [self class], [database lastErrorCode], [database lastErrorMessage]);
  209. }
  210. }
  211. @end