// // $Id$ // // SPLogger.m // sequel-pro // // Created by Rowan Beentje on 17/06/2009. // Copyright 2009 Rowan Beentje. All rights reserved. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // More info at #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", ...] * a file will be created on the user's desktop including timestamps and * the log message. * This allows use of fine-grained and detailed logging, without asking the user * to copy text from a console log via NSLog. * As each log line must by synched to disk as soon as it is received, for safety, * this class can add a performance hit when lots of logging is used. */ @implementation SPLogger /* * Returns the shared logger object. */ + (SPLogger *)logger { @synchronized(self) { if (logger == nil) { logger = [[super allocWithZone:NULL] init]; } } return logger; } #pragma mark - #pragma mark Initialisation and teardown + (id)allocWithZone:(NSZone *)zone { @synchronized(self) { return [[self logger] retain]; } } - (id)copyWithZone:(NSZone *)zone { return self; } - (id)retain { return self; } - (NSUInteger)retainCount { return NSUIntegerMax; } - (void)release {} - (id)autorelease { return self; } - (id)init { if ((self = [super init])) { dumpLeaksOnTermination = NO; initializedSuccessfully = YES; } return self; } #pragma mark - #pragma mark Logging functions - (void)log:(NSString *)theString, ... { if (!initializedSuccessfully) return; if (!logFileHandle) [self _initLogFile]; // Extract any supplied arguments and build the formatted log string va_list arguments; va_start(arguments, theString); NSString *logString = [[NSString alloc] initWithFormat:theString arguments:arguments]; 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 synchronizeFile]; [logString release]; } - (void)setDumpLeaksOnTermination { 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]]; } @end