From d04a1612e5c26e2752d6c30c5aaf5289d01e90a9 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sat, 24 Apr 2010 22:20:04 +0000 Subject: Enhance SPLogger by adding the ability to run leaks() against the application upon termination with the result being written to /tmp. To enable call setDumpLeaksOnTermination on SPLogger. Note, that SPLogger has been restructed to accommodate calling this method without writing the additional log file to the user's desktop. Could probably be enhanced a bit by someone with better C skills than me. --- Source/SPLogger.h | 41 ++++++++++++- Source/SPLogger.m | 154 +++++++++++++++++++++++++++++++++++++------------ Source/TableDocument.m | 9 ++- 3 files changed, 162 insertions(+), 42 deletions(-) diff --git a/Source/SPLogger.h b/Source/SPLogger.h index ac9b9a04..09fbfd53 100644 --- a/Source/SPLogger.h +++ b/Source/SPLogger.h @@ -27,13 +27,50 @@ @interface SPLogger : NSObject { + /** + * Dump leaks on termination flag. + */ + BOOL dumpLeaksOnTermination; + + /** + * Log file initialized successfully flag. + */ BOOL initializedSuccessfully; + + /** + * Log file handle. + */ NSFileHandle *logFileHandle; } +/** + * Returns the shared logger. + * + * @return The logger instance + */ + (SPLogger *)logger; -- (void) outputTimeString; -- (void) log:(NSString *)theString, ...; +/** + * Tells the logger to dump leaks analysis upon app termination. + */ +- (void)setDumpLeaksOnTermination; + +/** + * Dumps the result of running leaks to the file '/tmp/sp.leaks..tmp'. + * + * Note, that to enable useful output, make sure the following environment variables are set to YES: + * + * MallocStackLogging + * MallocStackLoggingNoCompact + * + * Also note that the application may take a while to terminate if it has been running for a significant + * period of time or has been handling large amounts of data. + */ +- (void)dumpLeaks; + +/** + * Logs the supplied string to the log file. + */ +- (void)log:(NSString *)theString, ...; @end diff --git a/Source/SPLogger.m b/Source/SPLogger.m index d492fe32..5b0e4a05 100644 --- a/Source/SPLogger.m +++ b/Source/SPLogger.m @@ -25,8 +25,20 @@ #import "SPLogger.h" +#include +#include +#include +#include + static SPLogger *logger = nil; +@interface SPLogger (PrivateAPI) + +- (void)_initLogFile; +- (void)_outputTimeString; + +@end + /** * This is a small class intended to aid in user issue debugging; by including * the header file, and using [[SPLogger logger] log:@"String with format", ...] @@ -77,41 +89,8 @@ static SPLogger *logger = nil; - (id)init { if ((self = [super init])) { - NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDesktopDirectory, NSUserDomainMask, YES); - NSString *logFilePath = [NSString stringWithFormat:@"%@/Sequel Pro Debug Log.txt", [paths objectAtIndex:0]]; - + dumpLeaksOnTermination = NO; initializedSuccessfully = YES; - - // Check if the debug file exists, and is writable - if ( [[NSFileManager defaultManager] fileExistsAtPath:logFilePath] ) { - if ( ![[NSFileManager defaultManager] isWritableFileAtPath:logFilePath] ) { - initializedSuccessfully = NO; - NSRunAlertPanel(@"Logging error", @"Log file exists but is not writeable; no debug log will be generated!", @"OK", nil, nil); - } - - // Otherwise try creating one - } else { - if ( ![[NSFileManager defaultManager] createFileAtPath:logFilePath contents:[NSData data] attributes:nil] ) { - initializedSuccessfully = NO; - NSRunAlertPanel(@"Logging error", @"Could not create log file for writing; no debug log will be generated!", @"OK", nil, nil); - } - } - - // Get a file handle to the file if possible - if (initializedSuccessfully) { - logFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath]; - if (!logFileHandle) { - initializedSuccessfully = NO; - NSRunAlertPanel(@"Logging error", @"Could not open log file for writing; no debug log will be generated!", @"OK", nil, nil); - } else { - [logFileHandle retain]; - [logFileHandle seekToEndOfFile]; - NSString *bundleName = [[NSFileManager defaultManager] displayNameAtPath:[[NSBundle mainBundle] bundlePath]]; - NSMutableString *logStart = [NSMutableString stringWithString:@"\n\n\n==========================================================================\n\n"]; - [logStart appendString:[NSString stringWithFormat:@"%@ (r%ld)\n", bundleName, (long)[[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] integerValue]]]; - [logFileHandle writeData:[logStart dataUsingEncoding:NSUTF8StringEncoding]]; - } - } } return self; @@ -120,9 +99,11 @@ static SPLogger *logger = nil; #pragma mark - #pragma mark Logging functions -- (void) log:(NSString *)theString, ... +- (void)log:(NSString *)theString, ... { if (!initializedSuccessfully) return; + + if (!logFileHandle) [self _initLogFile]; // Extract any supplied arguments and build the formatted log string va_list arguments; @@ -131,16 +112,113 @@ static SPLogger *logger = nil; va_end(arguments); // Write the log line, forcing an immediate write to disk to ensure logging - [logFileHandle writeData:[[NSString stringWithFormat:@"%@ %@\n", [[NSDate date] descriptionWithCalendarFormat:@"%H:%M:%S" timeZone:nil locale:[[NSUserDefaults standardUserDefaults] dictionaryRepresentation]], logString] dataUsingEncoding:NSUTF8StringEncoding]]; + [logFileHandle writeData:[[NSString stringWithFormat:@"%@ %@\n", [[NSDate date] descriptionWithCalendarFormat:@"%H:%M:%S" timeZone:nil locale:[[NSUserDefaults standardUserDefaults] dictionaryRepresentation]], logString] dataUsingEncoding:NSUTF8StringEncoding]]; [logFileHandle synchronizeFile]; [logString release]; } -- (void) outputTimeString +- (void)setDumpLeaksOnTermination { - if (!initializedSuccessfully) return; + dumpLeaksOnTermination = YES; +} + +- (void)dumpLeaks +{ + if (dumpLeaksOnTermination) { + + // Remove old leaks logs + int count, i; + int isSPLeaksLog(); + struct direct **files; + + count = scandir("/tmp", &files, isSPLeaksLog, NULL); + + char fpath[32]; + + for (i = 0; i < count; i++) + { + snprintf(fpath, sizeof(fpath), "/tmp/%s", files[i]->d_name); + + if (remove(fpath) != 0) { + printf("Unable to remove Sequel Pro leaks log '%s'", files[i]->d_name); + } + } + + FILE *fp; + FILE *fp2; + size_t len; + + char cmd[32], file[32], buf[512]; + + snprintf(cmd, sizeof(cmd), "/usr/bin/leaks %d", getpid()); + snprintf(file, sizeof(file), "/tmp/sp.leaks.%d.tmp", getpid()); + + // Write new leaks log + if ((fp = popen(cmd, "r")) && (fp2 = fopen(file, "w"))) { + + while(len = fread(buf, 1, sizeof(buf), fp)) + { + fwrite(buf, 1, len, fp2); + } + + pclose(fp); + } + } +} + +int isSPLeaksLog(struct direct *entry) +{ + return (strstr(entry->d_name, "sp.leaks") != NULL); +} + +- (void)_initLogFile +{ + NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDesktopDirectory, NSUserDomainMask, YES); + NSString *logFilePath = [NSString stringWithFormat:@"%@/Sequel Pro Debug Log.log", [paths objectAtIndex:0]]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + + // Check if the debug file exists, and is writable + if ([fileManager fileExistsAtPath:logFilePath]) { + if (![fileManager isWritableFileAtPath:logFilePath]) { + initializedSuccessfully = NO; + NSRunAlertPanel(@"Logging error", @"Log file exists but is not writeable; no debug log will be generated!", @"OK", nil, nil); + } + // Otherwise try creating one + } + else { + if (![fileManager createFileAtPath:logFilePath contents:[NSData data] attributes:nil]) { + initializedSuccessfully = NO; + NSRunAlertPanel(@"Logging error", @"Could not create log file for writing; no debug log will be generated!", @"OK", nil, nil); + } + } + + // Get a file handle to the file if possible + if (initializedSuccessfully) { + logFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath]; + + if (!logFileHandle) { + initializedSuccessfully = NO; + NSRunAlertPanel(@"Logging error", @"Could not open log file for writing; no debug log will be generated!", @"OK", nil, nil); + } + else { + [logFileHandle retain]; + [logFileHandle seekToEndOfFile]; + + NSString *bundleName = [fileManager displayNameAtPath:[[NSBundle mainBundle] bundlePath]]; + NSMutableString *logStart = [NSMutableString stringWithString:@"\n\n\n==========================================================================\n\n"]; + + [logStart appendString:[NSString stringWithFormat:@"%@ (r%ld)\n", bundleName, (long)[[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] integerValue]]]; + [logFileHandle writeData:[logStart dataUsingEncoding:NSUTF8StringEncoding]]; + } + } +} +- (void)_outputTimeString +{ + if (!initializedSuccessfully) return; + [logFileHandle writeData:[[NSString stringWithFormat:@"Launched at %@\n\n", [[NSDate date] description]] dataUsingEncoding:NSUTF8StringEncoding]]; } diff --git a/Source/TableDocument.m b/Source/TableDocument.m index 0701341d..3cda7ad1 100644 --- a/Source/TableDocument.m +++ b/Source/TableDocument.m @@ -56,6 +56,7 @@ #import "SPAlertSheets.h" #import "SPConstants.h" #import "SPMainThreadTrampoline.h" +#import "SPLogger.h" @interface TableDocument (PrivateAPI) @@ -112,7 +113,7 @@ statusValues = nil; printThread = nil; } - + return self; } @@ -241,7 +242,7 @@ [tableWindow addChildWindow:taskProgressWindow ordered:NSWindowAbove]; [taskProgressWindow setReleasedWhenClosed:YES]; [taskProgressWindow setContentView:taskProgressLayer]; - [self centerTaskWindow]; + [self centerTaskWindow]; } /** @@ -2571,6 +2572,10 @@ } [tablesListInstance selectionShouldChangeInTableView:nil]; + + // Note that this call does not need to be removed in release builds as leaks analysis output is only + // dumped if [[SPLogger logger] setDumpLeaksOnTermination]; has been called first. + [[SPLogger logger] dumpLeaks]; } #pragma mark - -- cgit v1.2.3