123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- //
- // FMDBLogger.m
- // SQLiteLogger
- //
- // CocoaLumberjack Demos
- //
- #import "FMDBLogger.h"
- #import "FMDatabase.h"
- @interface FMDBLogger ()
- - (void)validateLogDirectory;
- - (void)openDatabase;
- @end
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark -
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- @interface FMDBLogEntry : NSObject {
- @public
- NSNumber * context;
- NSNumber * level;
- NSString * message;
- NSDate * timestamp;
- }
- - (id)initWithLogMessage:(DDLogMessage *)logMessage;
- @end
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark -
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- @implementation FMDBLogEntry
- - (id)initWithLogMessage:(DDLogMessage *)logMessage
- {
- if ((self = [super init]))
- {
- context = @(logMessage->_context);
- level = @(logMessage->_flag);
- message = logMessage->_message;
- timestamp = logMessage->_timestamp;
- }
- return self;
- }
- @end
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark -
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- @implementation FMDBLogger
- - (id)initWithLogDirectory:(NSString *)aLogDirectory
- {
- if ((self = [super init]))
- {
- logDirectory = [aLogDirectory copy];
-
- pendingLogEntries = [[NSMutableArray alloc] initWithCapacity:_saveThreshold];
-
- [self validateLogDirectory];
- [self openDatabase];
- }
-
- return self;
- }
- - (void)validateLogDirectory
- {
- // Validate log directory exists or create the directory.
-
- BOOL isDirectory;
- if ([[NSFileManager defaultManager] fileExistsAtPath:logDirectory isDirectory:&isDirectory])
- {
- if (!isDirectory)
- {
- NSLog(@"%@: %@ - logDirectory(%@) is a file!", [self class], THIS_METHOD, logDirectory);
-
- logDirectory = nil;
- }
- }
- else
- {
- NSError *error = nil;
-
- BOOL result = [[NSFileManager defaultManager] createDirectoryAtPath:logDirectory
- withIntermediateDirectories:YES
- attributes:nil
- error:&error];
- if (!result)
- {
- NSLog(@"%@: %@ - Unable to create logDirectory(%@) due to error: %@",
- [self class], THIS_METHOD, logDirectory, error);
-
- logDirectory = nil;
- }
- }
- }
- - (void)openDatabase
- {
- if (logDirectory == nil)
- {
- return;
- }
-
- NSString *path = [logDirectory stringByAppendingPathComponent:@"log.sqlite"];
-
- database = [[FMDatabase alloc] initWithPath:path];
-
- if (![database open])
- {
- NSLog(@"%@: Failed opening database!", [self class]);
-
- database = nil;
-
- return;
- }
-
- NSString *cmd1 = @"CREATE TABLE IF NOT EXISTS logs (context integer, "
- "level integer, "
- "message text, "
- "timestamp double)";
- [database executeUpdate:cmd1];
- if ([database hadError])
- {
- NSLog(@"%@: Error creating table: code(%d): %@",
- [self class], [database lastErrorCode], [database lastErrorMessage]);
-
- database = nil;
- }
-
- NSString *cmd2 = @"CREATE INDEX IF NOT EXISTS timestamp ON logs (timestamp)";
-
- [database executeUpdate:cmd2];
- if ([database hadError])
- {
- NSLog(@"%@: Error creating index: code(%d): %@",
- [self class], [database lastErrorCode], [database lastErrorMessage]);
-
- database = nil;
- }
-
- [database setShouldCacheStatements:YES];
- }
- #pragma mark AbstractDatabaseLogger Overrides
- - (BOOL)db_log:(DDLogMessage *)logMessage
- {
- // You may be wondering, how come we don't just do the insert here and be done with it?
- // Is the buffering really needed?
- //
- // From the SQLite FAQ:
- //
- // (19) INSERT is really slow - I can only do few dozen INSERTs per second
- //
- // Actually, SQLite will easily do 50,000 or more INSERT statements per second on an average desktop computer.
- // But it will only do a few dozen transactions per second. Transaction speed is limited by the rotational
- // speed of your disk drive. A transaction normally requires two complete rotations of the disk platter, which
- // on a 7200RPM disk drive limits you to about 60 transactions per second.
- //
- // Transaction speed is limited by disk drive speed because (by default) SQLite actually waits until the data
- // really is safely stored on the disk surface before the transaction is complete. That way, if you suddenly
- // lose power or if your OS crashes, your data is still safe. For details, read about atomic commit in SQLite.
- //
- // By default, each INSERT statement is its own transaction. But if you surround multiple INSERT statements
- // with BEGIN...COMMIT then all the inserts are grouped into a single transaction. The time needed to commit
- // the transaction is amortized over all the enclosed insert statements and so the time per insert statement
- // is greatly reduced.
-
- FMDBLogEntry *logEntry = [[FMDBLogEntry alloc] initWithLogMessage:logMessage];
-
- [pendingLogEntries addObject:logEntry];
-
- // Return YES if an item was added to the buffer.
- // Return NO if the logMessage was ignored.
-
- return YES;
- }
- - (void)db_save
- {
- if ([pendingLogEntries count] == 0)
- {
- // Nothing to save.
- // The superclass won't likely call us if this is the case, but we're being cautious.
- return;
- }
-
- BOOL saveOnlyTransaction = ![database inTransaction];
-
- if (saveOnlyTransaction)
- {
- [database beginTransaction];
- }
-
- NSString *cmd = @"INSERT INTO logs (context, level, message, timestamp) VALUES (?, ?, ?, ?)";
-
- for (FMDBLogEntry *logEntry in pendingLogEntries)
- {
- [database executeUpdate:cmd, logEntry->context,
- logEntry->level,
- logEntry->message,
- logEntry->timestamp];
- }
-
- [pendingLogEntries removeAllObjects];
-
- if (saveOnlyTransaction)
- {
- [database commit];
-
- if ([database hadError])
- {
- NSLog(@"%@: Error inserting log entries: code(%d): %@",
- [self class], [database lastErrorCode], [database lastErrorMessage]);
- }
- }
- }
- - (void)db_delete
- {
- if (_maxAge <= 0.0)
- {
- // Deleting old log entries is disabled.
- // The superclass won't likely call us if this is the case, but we're being cautious.
- return;
- }
-
- BOOL deleteOnlyTransaction = ![database inTransaction];
-
- NSDate *maxDate = [NSDate dateWithTimeIntervalSinceNow:(-1.0 * _maxAge)];
-
- [database executeUpdate:@"DELETE FROM logs WHERE timestamp < ?", maxDate];
-
- if (deleteOnlyTransaction)
- {
- if ([database hadError])
- {
- NSLog(@"%@: Error deleting log entries: code(%d): %@",
- [self class], [database lastErrorCode], [database lastErrorMessage]);
- }
- }
- }
- - (void)db_saveAndDelete
- {
- [database beginTransaction];
-
- [self db_delete];
- [self db_save];
-
- [database commit];
-
- if ([database hadError])
- {
- NSLog(@"%@: Error: code(%d): %@",
- [self class], [database lastErrorCode], [database lastErrorMessage]);
- }
- }
- @end
|