FMDBLogger.m 7.9 KB

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