//
// $Id$
//
// SPLogger.m
// sequel-pro
//
// Created by Rowan Beentje on June 17, 2009.
// Copyright (c) 2009 Rowan Beentje. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// More info at
#import "SPLogger.h"
#import
#import
#import
#import
#import
static SPLogger *logger = nil;
@interface SPLogger ()
- (void)_initLogFile;
- (void)_outputTimeString;
int _isSPLeaksLog(const struct direct *entry);
@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
@synthesize dumpLeaksOnTermination;
@synthesize removeOldLeakDumpsOnTermination;
/*
* 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];
}
return nil;
}
- (id)init
{
if ((self = [super init])) {
initializedSuccessfully = YES;
[self setDumpLeaksOnTermination:NO];
[self setRemoveOldLeakDumpsOnTermination: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, and
// synchronised to allow use across multiple executables or their frameworks.
[logFileHandle synchronizeFile];
[logFileHandle seekToEndOfFile];
[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)dumpLeaks
{
if ([self dumpLeaksOnTermination]) {
char *lgn;
struct passwd *pw;
boolean_t hdir = FALSE;
// Determine where to write the log to
if ((lgn = getlogin()) == NULL || (pw = getpwnam(lgn)) == NULL) {
fprintf(stderr, "Unable to get user info, falling back to /tmp\n");
}
else {
hdir = TRUE;
}
// If required remove old logs
if ([self removeOldLeakDumpsOnTermination]) {
int cnt, cnt2, i;
struct direct **files;
cnt = scandir("/tmp", &files, _isSPLeaksLog, NULL);
char fpath[32], fpath2[32], fpath3[64];
for (i = 0; i < cnt; i++)
{
snprintf(fpath, sizeof(fpath), "/tmp/%s", files[i]->d_name);
if (remove(fpath) != 0) {
printf("Unable to remove Sequel Pro leaks log '%s'\n", files[i]->d_name);
}
}
free(&files);
if (hdir) {
snprintf(fpath2, sizeof(fpath2), "%s/Desktop", pw->pw_dir);
cnt2 = scandir(fpath2, &files, _isSPLeaksLog, NULL);
for (i = 0; i < cnt2; i++)
{
snprintf(fpath3, sizeof(fpath3), "%s/%s", fpath2, files[i]->d_name);
if (remove(fpath3) != 0) {
printf("Unable to remove Sequel Pro leaks log '%s'\n", files[i]->d_name);
}
}
}
}
size_t len;
FILE *fp, *fp2;
char cmd[32], file[64], buf[512];
snprintf(cmd, sizeof(cmd), "/usr/bin/leaks %d", getpid());
snprintf(file, sizeof(file), (hdir) ? "%s/Desktop/sp.leaks.%d.log" : "%s/sp.leaks.%d.log", (hdir) ? pw->pw_dir : "/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);
}
}
}
#pragma mark -
#pragma mark Private API
- (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 appendFormat:@"%@ (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]];
}
int _isSPLeaksLog(const struct direct *entry)
{
return (strstr(entry->d_name, "sp.leaks") != NULL);
}
@end