/** Enterprise Control Configuration and Logging Copyright (C) 2012 Free Software Foundation, Inc. Written by: Richard Frith-Macdonald Date: Febrary 2010 Originally developed from 1996 to 2012 by Brainstorm, and donated to the FSF. This file is part of the GNUstep project. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. */ #import #import #import "EcProcess.h" #import "EcLogger.h" NSString* const EcLoggersDidChangeNotification = @"EcLoggersDidChangeNotification"; @implementation EcLogger static Class loggersClass; static NSLock *loggersLock; static NSMutableArray *loggers; static NSArray *modes; + (void) initialize { if (self == [EcLogger class]) { id objects[1]; loggersLock = [NSLock new]; loggers = [[NSMutableArray alloc] initWithCapacity: 6]; objects[0] = NSDefaultRunLoopMode; modes = [[NSArray alloc] initWithObjects: objects count: 1]; [self setFactory: self]; } } + (EcLogger*) loggerForType: (EcLogType)t { unsigned count; EcLogger *logger; [loggersLock lock]; count = [loggers count]; while (count-- > 0) { logger = [loggers objectAtIndex: count]; if (logger->type == t) { [loggersLock unlock]; return logger; } } logger = [[loggersClass alloc] init]; if (logger != nil) { logger->lock = [NSRecursiveLock new]; logger->type = t; logger->key = [cmdLogKey(t) copy]; logger->flushKey = [[NSString alloc] initWithFormat: @"%@Flush", logger->key]; logger->serverKey = [[NSString alloc] initWithFormat: @"%@Server", logger->key]; logger->interval = 10.0; logger->size = 8 * 1024; logger->message = [[NSMutableString alloc] initWithCapacity: 2048]; if (LT_ERROR == t || LT_AUDIT == t || LT_ALERT == t) { logger->shouldForward = YES; } [[NSNotificationCenter defaultCenter] addObserver: logger selector: @selector(update) name: NSUserDefaultsDidChangeNotification object: [EcProc cmdDefaults]]; [logger update]; [loggers addObject: logger]; RELEASE(logger); } [loggersLock unlock]; return logger; } + (void) setFactory: (Class)c { NSMutableArray *old = nil; if (Nil == c) c = [self class]; NSAssert([c isSubclassOfClass: self], NSInvalidArgumentException); [loggersLock lock]; if (loggersClass != c) { old = loggers; loggers = [NSMutableArray new]; loggersClass = c; } [loggersLock unlock]; if (nil != old) { [old makeObjectsPerformSelector: @selector(flush)]; [old release]; [[NSNotificationCenter defaultCenter] postNotificationName: EcLoggersDidChangeNotification object: self]; } } /* Should only be called on main thread, but doesn't matter. */ - (void) cmdGnip: (id )from sequence: (unsigned)num extra: (NSData*)data { return; } /* * When connecting to a logging server, we need to register so it * knows who we are. * Should only be called on main thread. */ - (void) cmdMadeConnectionToServer: (NSString*)name { id server; server = (id)[EcProc server: name]; [server registerClient: self name: cmdLogName()]; } /* Should only be called on main thread, but doesn't matter. */ - (void) cmdPing: (id )from sequence: (unsigned)num extra: (NSData*)data { [from cmdGnip: self sequence: num extra: nil]; } /* Should only be called on main thread. */ - (oneway void) cmdQuit: (NSInteger)status { [EcProc cmdQuit: status]; } - (void) dealloc { [self flush]; [timer invalidate]; [[NSNotificationCenter defaultCenter] removeObserver: self]; RELEASE(key); RELEASE(flushKey); RELEASE(serverKey); RELEASE(serverName); RELEASE(message); RELEASE(lock); [super dealloc]; } - (NSString*) description { NSMutableString *s = [NSMutableString stringWithCapacity: 256]; [lock lock]; if (NO == shouldForward) { [s appendFormat: @"%@ output via NSLog only.\n", key]; } else if (size == 0) { [s appendFormat: @"%@ output is immediate.\n", key]; } else if (interval > 0.0) { [s appendFormat: @"%@ flushed every %g seconds", key, interval]; [s appendFormat: @" or with a %u byte buffer.\n", size]; if (timer != nil) { [s appendFormat: @"Next flush - %@\n", [timer fireDate]]; } } else { [s appendFormat: @"%@ flushed with a %u byte buffer.\n", key, size]; } [lock unlock]; return s; } /** * Internal flush operation ... writes data out from us, but * doesn't try any further. Only called in main thread. */ - (void) _flush { NSString *str = nil; if (inFlush == YES) { return; } [lock lock]; if (NO == inFlush && [message length] > 0) { inFlush = YES; str = [message copy]; [message setString: @""]; } [lock unlock]; if (nil != str) { BOOL ok = YES; if (LT_DEBUG != type) { if (nil == serverName) { id server; NS_DURING { server = (id)[EcProc cmdNewServer]; } NS_HANDLER { server = nil; NSLog(@"Exception contacting Command server: %@\n", localException); } NS_ENDHANDLER if (server != nil) { NS_DURING { [server logMessage: str type: type for: EcProc]; } NS_HANDLER { NSLog(@"Exception sending info to logger: %@", localException); ok = NO; } NS_ENDHANDLER } else { ok = NO; } } else { id server; NS_DURING { server = (id) [EcProc server: serverName]; } NS_HANDLER { server = nil; NSLog(@"Exception contacting logging server: %@\n", localException); } NS_ENDHANDLER if (server != nil) { NS_DURING { [server logMessage: str type: type for: self]; } NS_HANDLER { NSLog(@"Exception sending info to %@: %@", serverName, localException); ok = NO; } NS_ENDHANDLER } else { ok = NO; } } } if (NO == ok) { if (nil == serverName) { NSLog(@"%@", str); } else { NSLog(@"Unable to log to %@ - %@", serverName, str); } DESTROY(str); /* * Flush any messages that might have been generated by * (or during) our failed attempt to contact server. */ [lock lock]; if ([message length] > 0) { str = [message copy]; [message setString: @""]; } [lock unlock]; if (nil != str) { NSLog(@"%@", str); } } RELEASE(str); [lock lock]; inFlush = NO; [lock unlock]; } } /* ONLY called in main thread. */ - (void) _externalFlush: (id)dummy { if (externalFlush == NO) { externalFlush = YES; [self _flush]; if (LT_DEBUG != type) { id server; if (serverName == nil) { NS_DURING { server = (id)[EcProc cmdNewServer]; } NS_HANDLER { server = nil; NSLog(@"Exception contacting Command server: %@\n", localException); } NS_ENDHANDLER } else { NS_DURING { server = (id) [EcProc server: serverName]; } NS_HANDLER { server = nil; NSLog(@"Exception contacting logging server: %@\n", localException); } NS_ENDHANDLER } if (server != nil) { NS_DURING { [server flush]; // Force round trip. } NS_HANDLER { NSLog(@"Exception flushing info to %@: %@", serverName, localException); } NS_ENDHANDLER } } externalFlush = NO; } } /** * External flush operation ... writes out data and asks any server * we write to to flush its data out too. */ - (void) flush { [self performSelectorOnMainThread: @selector(_externalFlush:) withObject: nil waitUntilDone: YES modes: modes]; } /* This is ONLY called in the main thread. */ - (void) _scheduleFlush: (id)reset { /* A non-nil value of reset means that we should reset to do * a flush real soon. */ [lock lock]; pendingFlush = NO; if (reset != nil) { if (timer != nil && [[timer fireDate] timeIntervalSinceNow] > 0.001) { [timer invalidate]; timer = nil; } if (timer == nil) { timer = [NSTimer scheduledTimerWithTimeInterval: 0.0001 target: self selector: @selector(_timeout:) userInfo: nil repeats: NO]; } } else if ([message length] >= size) { /* * Buffer too large - schedule immediate flush. */ if (timer != nil && [[timer fireDate] timeIntervalSinceNow] > 0.001) { [timer invalidate]; timer = nil; } if (timer == nil) { timer = [NSTimer scheduledTimerWithTimeInterval: 0.0001 target: self selector: @selector(_timeout:) userInfo: nil repeats: NO]; } } else if (interval > 0.0 && timer == nil) { /* * No timer running - so schedule one to output the debug info. */ timer = [NSTimer scheduledTimerWithTimeInterval: interval target: self selector: @selector(_timeout:) userInfo: nil repeats: NO]; } [lock unlock]; } - (void) log: (NSString*)fmt arguments: (va_list)args { NSString *format; format = [[NSString alloc] initWithFormat: @"%@ - %@", cmdLogKey(type), fmt]; NSLogv(format, args); RELEASE(format); if (YES == shouldForward) { CREATE_AUTORELEASE_POOL(arp); NSString *text; BOOL shouldFlush = NO; format = cmdLogFormat(type, fmt); text = [NSString stringWithFormat: format arguments: args]; [lock lock]; if (message == nil) { message = [[NSMutableString alloc] initWithCapacity: 1024]; } [message appendString: text]; if ([message length] >= size || (interval > 0.0 && timer == nil)) { if (NO == pendingFlush) { shouldFlush = YES; pendingFlush = YES; } } [lock unlock]; if (YES == shouldFlush) { [self performSelectorOnMainThread: @selector(_scheduleFlush:) withObject: nil waitUntilDone: NO modes: modes]; } RELEASE(arp); } } /* Should only be called on main thread. */ - (void) _timeout: (NSTimer*)t { if (t == nil) { [timer invalidate]; } timer = nil; [self _flush]; } /* Should only execute on main thread. */ - (void) update { NSUserDefaults *defs; NSString *str; if (NO == [NSThread isMainThread]) { [self performSelectorOnMainThread: @selector(update) withObject: nil waitUntilDone: NO modes: modes]; return; } defs = [EcProc cmdDefaults]; /* * If there is a server specified for this debug logger, we want it * registered so we can use it - otherwise we will default to using * the Command server for logging. */ str = [defs stringForKey: serverKey]; if ([str isEqual: @""] == YES) { str = nil; // An empty string means no server is used. } if ([serverName isEqual: str] == NO) { if (serverName != nil) { [EcProc removeServerFromList: serverName]; } ASSIGN(serverName, str); if (serverName != nil) { [EcProc addServerToList: serverName for: self]; } } /* Is the program to flush at intervals or at * a particular buffer size (or both)? */ str = [defs stringForKey: flushKey]; if (str == nil) { str = [defs stringForKey: @"DefaultFlush"]; // Default settings. } if (str != nil) { NSScanner *scanner = [NSScanner scannerWithString: str]; float f; int i; if ([scanner scanFloat: &f] == YES) { interval = (floor(interval * 1000)) / 1000.0; if (f < 0.0) interval = 0.0; else interval = f; } if (([scanner scanString: @":" intoString: 0] == YES) && ([scanner scanInt: &i] == YES)) { if (i < 0) size = 0; else size = i*1024; } /* * Ensure new values take effect real soon. */ if (NO == pendingFlush) { [self performSelectorOnMainThread: @selector(_scheduleFlush:) withObject: self waitUntilDone: NO modes: modes]; } } } @end