mirror of https://github.com/axmolengine/axmol.git
2867 lines
95 KiB
Objective-C
2867 lines
95 KiB
Objective-C
/*
|
|
* LoggerClient.m
|
|
*
|
|
* version 1.5-RC2 22-NOV-2013
|
|
*
|
|
* Main implementation of the NSLogger client side code
|
|
* Part of NSLogger (client side)
|
|
* https://github.com/fpillet/NSLogger
|
|
*
|
|
* BSD license follows (http://www.opensource.org/licenses/bsd-license.php)
|
|
*
|
|
* Copyright (c) 2010-2013 Florent Pillet All Rights Reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification,
|
|
* are permitted provided that the following conditions are met:
|
|
*
|
|
* Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer. Redistributions in
|
|
* binary form must reproduce the above copyright notice, this list of
|
|
* conditions and the following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution. Neither the name of Florent
|
|
* Pillet nor the names of its contributors may be used to endorse or promote
|
|
* products derived from this software without specific prior written
|
|
* permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
|
|
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
#import <sys/time.h>
|
|
#import <arpa/inet.h>
|
|
#import <stdlib.h>
|
|
|
|
#import "LoggerClient.h"
|
|
#import "LoggerCommon.h"
|
|
|
|
#if !TARGET_OS_IPHONE
|
|
#import <sys/types.h>
|
|
#import <sys/sysctl.h>
|
|
#import <sys/utsname.h>
|
|
#import <dlfcn.h>
|
|
#elif ALLOW_COCOA_USE
|
|
#import <UIKit/UIKit.h>
|
|
#endif
|
|
#import <fcntl.h>
|
|
|
|
/* --------------------------------------------------------------------------------
|
|
* IMPLEMENTATION NOTES:
|
|
*
|
|
* The logger runs in a separate thread. It is written
|
|
* in straight C for maximum compatibility with all runtime environments
|
|
* (does not use the Objective-C runtime, only uses unix and CoreFoundation
|
|
* calls, except for get the thread name and device information, but these
|
|
* can be disabled by setting ALLOW_COCOA_USE to 0).
|
|
*
|
|
* It is suitable for use in both Cocoa and low-level code. It does not activate
|
|
* Cocoa multi-threading (no call to [NSThread detachNewThread...]). You can start
|
|
* logging very early (as soon as your code starts running), logs will be
|
|
* buffered and sent to the log viewer as soon as a connection is acquired.
|
|
* This makes the logger suitable for use in conditions where you usually
|
|
* don't have a connection to a remote machine yet (early wakeup, network
|
|
* down, etc).
|
|
*
|
|
* When you call one of the public logging functions, the logger is designed
|
|
* to return to your application as fast as possible. It enqueues logs to
|
|
* send for processing by its own thread, while your application keeps running.
|
|
*
|
|
* The logger does buffer logs while not connected to a desktop
|
|
* logger. It uses Bonjour to find a logger on the local network, and can
|
|
* optionally connect to a remote logger identified by an IP address / port
|
|
* or a Host Name / port.
|
|
*
|
|
* The logger can optionally output its logs to the console, like NSLog().
|
|
*
|
|
* The logger can optionally buffer its logs to a file for which you specify the
|
|
* full path. Upon connection to the desktop viewer, the file contents are
|
|
* transmitted to the viewer prior to sending new logs. When the whole file
|
|
* content has been transmitted, it is emptied.
|
|
*
|
|
* Multiple loggers can coexist at the same time. You can perfectly use a
|
|
* logger for your debug traces, and another that connects remotely to help
|
|
* diagnose issues while the application runs on your user's device.
|
|
*
|
|
* The logger can optionally capture stdout and stderr. When running an
|
|
* application from the IDE, this will automatically capture everything that
|
|
* goes into the debugger console log, and insert it in the stream of logs
|
|
* sent to the viewer.
|
|
*
|
|
* Using the logger's flexible packet format, you can customize logging by
|
|
* creating your own log types, and customize the desktop viewer to display
|
|
* runtime information panels for your application.
|
|
* --------------------------------------------------------------------------------
|
|
*/
|
|
|
|
/* Logger internal debug flags */
|
|
// Set to 0 to disable internal debug completely
|
|
// Set to 1 to activate console logs when running the logger itself
|
|
// Set to 2 to see every logging call issued by the app, too
|
|
#define LOGGER_DEBUG 0
|
|
#ifdef NSLog
|
|
#undef NSLog
|
|
#endif
|
|
|
|
// Internal debugging stuff for the logger itself
|
|
#if LOGGER_DEBUG
|
|
#define LOGGERDBG LoggerDbg
|
|
#if LOGGER_DEBUG > 1
|
|
#define LOGGERDBG2 LoggerDbg
|
|
#else
|
|
#define LOGGERDBG2(format, ...) do{}while(0)
|
|
#endif
|
|
// Internal logging function prototype
|
|
static void LoggerDbg(CFStringRef format, ...);
|
|
#else
|
|
#define LOGGERDBG(format, ...) do{}while(0)
|
|
#define LOGGERDBG2(format, ...) do{}while(0)
|
|
#endif
|
|
|
|
// small set of macros for proper ARC/non-ARC compilation support
|
|
// with added cruft to support non-clang compilers
|
|
#undef LOGGER_ARC_MACROS_DEFINED
|
|
#if defined(__has_feature)
|
|
#if __has_feature(objc_arc)
|
|
#define CAST_TO_CFSTRING __bridge CFStringRef
|
|
#define CAST_TO_NSSTRING __bridge NSString *
|
|
#define CAST_TO_CFDATA __bridge CFDataRef
|
|
#define RELEASE(obj) do{}while(0)
|
|
#define LOGGER_ARC_MACROS_DEFINED
|
|
#endif
|
|
#endif
|
|
#if !defined(LOGGER_ARC_MACROS_DEFINED)
|
|
#define CAST_TO_CFSTRING CFStringRef
|
|
#define CAST_TO_NSSTRING NSString *
|
|
#define CAST_TO_CFDATA CFDataRef
|
|
#define RELEASE(obj) [obj release]
|
|
#endif
|
|
#undef LOGGER_ARC_MACROS_DEFINED
|
|
|
|
/* Local prototypes */
|
|
static void LoggerFlushAllOnExit(void);
|
|
static void* LoggerWorkerThread(Logger *logger);
|
|
static void LoggerWriteMoreData(Logger *logger);
|
|
static void LoggerPushMessageToQueue(Logger *logger, CFDataRef message);
|
|
|
|
// Bonjour management
|
|
static void LoggerStartBonjourBrowsing(Logger *logger);
|
|
static void LoggerStopBonjourBrowsing(Logger *logger);
|
|
static BOOL LoggerBrowseBonjourForServices(Logger *logger, CFStringRef domainName);
|
|
static void LoggerServiceBrowserCallBack(CFNetServiceBrowserRef browser, CFOptionFlags flags, CFTypeRef domainOrService, CFStreamError* error, void *info);
|
|
|
|
// Reachability and reconnect timer
|
|
static void LoggerRemoteSettingsChanged(Logger *logger);
|
|
static void LoggerStartReachabilityChecking(Logger *logger);
|
|
static void LoggerStopReachabilityChecking(Logger *logger);
|
|
static void LoggerReachabilityCallBack(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info);
|
|
static void LoggerStartReconnectTimer(Logger *logger);
|
|
static void LoggerStopReconnectTimer(Logger *logger);
|
|
static void LoggerTimedReconnectCallback(CFRunLoopTimerRef timer, void *info);
|
|
|
|
// Connection & stream management
|
|
static void LoggerTryConnect(Logger *logger);
|
|
static void LoggerWriteStreamTerminated(Logger *logger);
|
|
static void LoggerWriteStreamCallback(CFWriteStreamRef ws, CFStreamEventType event, void* info);
|
|
|
|
// File buffering
|
|
static void LoggerCreateBufferWriteStream(Logger *logger);
|
|
static void LoggerCreateBufferReadStream(Logger *logger);
|
|
static void LoggerEmptyBufferFile(Logger *logger);
|
|
static void LoggerFileBufferingOptionsChanged(Logger *logger);
|
|
static void LoggerFlushQueueToBufferStream(Logger *logger, BOOL firstEntryIsClientInfo);
|
|
|
|
// Encoding functions
|
|
static void LoggerPushClientInfoToFrontOfQueue(Logger *logger);
|
|
static void LoggerMessageAddTimestampAndThreadID(CFMutableDataRef encoder);
|
|
|
|
static CFMutableDataRef LoggerMessageCreate(int32_t seq);
|
|
static void LoggerMessageAddInt32(CFMutableDataRef encoder, int32_t anInt, int key);
|
|
#if __LP64__
|
|
static void LoggerMessageAddInt64(CFMutableDataRef data, int64_t anInt, int key);
|
|
#endif
|
|
static void LoggerMessageAddString(CFMutableDataRef encoder, CFStringRef aString, int key);
|
|
static void LoggerMessageAddData(CFMutableDataRef encoder, CFDataRef theData, int key, int partType);
|
|
static uint32_t LoggerMessageGetSeq(CFDataRef message);
|
|
|
|
/* Static objects */
|
|
static CFMutableArrayRef sLoggersList;
|
|
static Logger* volatile sDefaultLogger = NULL;
|
|
static Boolean sAtexitFunctionSet = FALSE;
|
|
static pthread_mutex_t sLoggersListMutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_mutex_t sDefaultLoggerMutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
// Console logging
|
|
static void LoggerStartGrabbingConsole(Logger *logger);
|
|
static void LoggerStopGrabbingConsole(Logger *logger);
|
|
static Logger ** consoleGrabbersList = NULL;
|
|
static unsigned consoleGrabbersListLength;
|
|
static unsigned numActiveConsoleGrabbers = 0;
|
|
static pthread_mutex_t consoleGrabbersMutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_t consoleGrabThread;
|
|
static int sConsolePipes[4] = { -1, -1, -1, -1 };
|
|
static int sSTDOUT = -1, sSTDERR = -1;
|
|
static int sSTDOUThadSIGPIPE, sSTDERRhadSIGPIPE;
|
|
|
|
// -----------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark Default logger
|
|
// -----------------------------------------------------------------------------
|
|
void LoggerSetDefaultLogger(Logger *defaultLogger)
|
|
{
|
|
pthread_mutex_lock(&sDefaultLoggerMutex);
|
|
sDefaultLogger = defaultLogger;
|
|
pthread_mutex_unlock(&sDefaultLoggerMutex);
|
|
}
|
|
|
|
Logger *LoggerGetDefaultLogger(void)
|
|
{
|
|
Logger *l = sDefaultLogger;
|
|
if (l == NULL)
|
|
{
|
|
pthread_mutex_lock(&sDefaultLoggerMutex);
|
|
l = sDefaultLogger;
|
|
if (l == NULL)
|
|
l = LoggerInit();
|
|
pthread_mutex_unlock(&sDefaultLoggerMutex);
|
|
}
|
|
return l;
|
|
}
|
|
|
|
Logger *LoggerCheckDefaultLogger(void)
|
|
{
|
|
return sDefaultLogger;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark Initialization and setup
|
|
// -----------------------------------------------------------------------------
|
|
Logger *LoggerInit(void)
|
|
{
|
|
LOGGERDBG(CFSTR("LoggerInit defaultLogger=%p"), sDefaultLogger);
|
|
|
|
Logger *logger = (Logger *)malloc(sizeof(Logger));
|
|
bzero(logger, sizeof(Logger));
|
|
|
|
logger->logQueue = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
|
|
pthread_mutex_init(&logger->logQueueMutex, NULL);
|
|
pthread_cond_init(&logger->logQueueEmpty, NULL);
|
|
|
|
logger->bonjourServiceBrowsers = CFArrayCreateMutable(NULL, 4, &kCFTypeArrayCallBacks);
|
|
logger->bonjourServices = CFArrayCreateMutable(NULL, 4, &kCFTypeArrayCallBacks);
|
|
|
|
// for now we don't grow the send buffer, just use one page of memory which should be enouh
|
|
// (bigger messages will be sent separately)
|
|
logger->sendBuffer = (uint8_t *)malloc(4096);
|
|
logger->sendBufferSize = 4096;
|
|
|
|
logger->options = LOGGER_DEFAULT_OPTIONS;
|
|
#if LOGGER_DEBUG
|
|
// when debugging NSLogger itself, don't hijack the system console
|
|
// as we are sending messages to it for display
|
|
logger->options &= ~kLoggerOption_CaptureSystemConsole;
|
|
#endif
|
|
|
|
logger->quit = NO;
|
|
|
|
// Add logger to the list of existing loggers
|
|
// Set this logger as the default logger is none exist already
|
|
pthread_mutex_lock(&sLoggersListMutex);
|
|
if (sLoggersList == NULL)
|
|
{
|
|
CFArrayCallBacks callbacks;
|
|
bzero(&callbacks, sizeof(callbacks));
|
|
sLoggersList = CFArrayCreateMutable(NULL, 0, &callbacks);
|
|
}
|
|
CFArrayAppendValue(sLoggersList, (const void *)logger);
|
|
if (sDefaultLogger == NULL)
|
|
sDefaultLogger = logger;
|
|
|
|
// Configure a low level exit() callback that will flush all connected loggers
|
|
if (!sAtexitFunctionSet)
|
|
{
|
|
atexit(&LoggerFlushAllOnExit);
|
|
sAtexitFunctionSet = TRUE;
|
|
}
|
|
pthread_mutex_unlock(&sLoggersListMutex);
|
|
|
|
return logger;
|
|
}
|
|
|
|
void LoggerSetOptions(Logger *logger, uint32_t options)
|
|
{
|
|
LOGGERDBG(CFSTR("LoggerSetOptions options=0x%08lx"), options);
|
|
|
|
// If we choose to log to system console
|
|
// make sure we are not configured to capture the system console
|
|
// When debugging NSLogger itself, we never capture the system console either
|
|
if (options & kLoggerOption_LogToConsole)
|
|
options &= (uint32_t)~kLoggerOption_CaptureSystemConsole;
|
|
|
|
if (logger == NULL)
|
|
logger = LoggerGetDefaultLogger();
|
|
if (logger != NULL)
|
|
logger->options = options;
|
|
}
|
|
|
|
void LoggerSetupBonjour(Logger *logger, CFStringRef bonjourServiceType, CFStringRef bonjourServiceName)
|
|
{
|
|
LOGGERDBG(CFSTR("LoggerSetupBonjour serviceType=%@ serviceName=%@"), bonjourServiceType, bonjourServiceName);
|
|
|
|
if (logger == NULL)
|
|
logger = LoggerGetDefaultLogger();
|
|
if (logger != NULL)
|
|
{
|
|
if (bonjourServiceType != NULL)
|
|
CFRetain(bonjourServiceType);
|
|
if (bonjourServiceName != NULL)
|
|
CFRetain(bonjourServiceName);
|
|
if (logger->bonjourServiceType != NULL)
|
|
CFRelease(logger->bonjourServiceType);
|
|
if (logger->bonjourServiceName != NULL)
|
|
CFRelease(logger->bonjourServiceName);
|
|
logger->bonjourServiceType = bonjourServiceType;
|
|
logger->bonjourServiceName = bonjourServiceName;
|
|
}
|
|
}
|
|
|
|
void LoggerSetViewerHost(Logger *logger, CFStringRef hostName, UInt32 port)
|
|
{
|
|
if (logger == NULL)
|
|
logger = LoggerGetDefaultLogger();
|
|
if (logger == NULL)
|
|
return;
|
|
|
|
CFStringRef previousHost = logger->host;
|
|
UInt32 previousPort = logger->port;
|
|
|
|
logger->host = NULL;
|
|
|
|
if (hostName != NULL)
|
|
{
|
|
logger->host = CFStringCreateCopy(NULL, hostName);
|
|
logger->port = port;
|
|
}
|
|
|
|
if (logger->remoteOptionsChangedSource != NULL &&
|
|
(logger->port != previousPort ||
|
|
((hostName == NULL) != (previousHost == NULL)) ||
|
|
(hostName != NULL && CFStringCompare(hostName, previousHost, kCFCompareCaseInsensitive) != kCFCompareEqualTo)))
|
|
CFRunLoopSourceSignal(logger->remoteOptionsChangedSource);
|
|
|
|
if (previousHost != NULL)
|
|
CFRelease(previousHost);
|
|
}
|
|
|
|
void LoggerSetBufferFile(Logger *logger, CFStringRef absolutePath)
|
|
{
|
|
if (logger == NULL)
|
|
{
|
|
logger = LoggerGetDefaultLogger();
|
|
if (logger == NULL)
|
|
return;
|
|
}
|
|
|
|
BOOL change = ((logger->bufferFile != NULL && absolutePath == NULL) ||
|
|
(logger->bufferFile == NULL && absolutePath != NULL) ||
|
|
(logger->bufferFile != NULL && absolutePath != NULL && CFStringCompare(logger->bufferFile, absolutePath, (CFStringCompareFlags) 0) != kCFCompareEqualTo));
|
|
if (change)
|
|
{
|
|
if (logger->bufferFile != NULL)
|
|
{
|
|
CFRelease(logger->bufferFile);
|
|
logger->bufferFile = NULL;
|
|
}
|
|
if (absolutePath != NULL)
|
|
logger->bufferFile = CFStringCreateCopy(NULL, absolutePath);
|
|
if (logger->bufferFileChangedSource != NULL)
|
|
CFRunLoopSourceSignal(logger->bufferFileChangedSource);
|
|
}
|
|
}
|
|
|
|
Logger *LoggerStart(Logger *logger)
|
|
{
|
|
// will do nothing if logger is already started
|
|
if (logger == NULL)
|
|
logger = LoggerGetDefaultLogger();
|
|
|
|
if (logger != NULL)
|
|
{
|
|
if (logger->workerThread == NULL)
|
|
{
|
|
dispatch_once(&logger->workerThreadInit, ^{
|
|
// Start the work thread which performs the Bonjour search,
|
|
// connects to the logging service and forwards the logs
|
|
LOGGERDBG(CFSTR("LoggerStart logger=%p"), logger);
|
|
pthread_create(&logger->workerThread, NULL, (void *(*)(void *))&LoggerWorkerThread, logger);
|
|
|
|
// Grab console output if required
|
|
if (logger->options & kLoggerOption_CaptureSystemConsole)
|
|
LoggerStartGrabbingConsole(logger);
|
|
});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOGGERDBG2(CFSTR("-> could not create logger"));
|
|
}
|
|
return logger;
|
|
}
|
|
|
|
void LoggerStop(Logger *logger)
|
|
{
|
|
LOGGERDBG(CFSTR("LoggerStop"));
|
|
|
|
pthread_mutex_lock(&sLoggersListMutex);
|
|
if (logger == NULL || logger == sDefaultLogger)
|
|
{
|
|
logger = sDefaultLogger;
|
|
sDefaultLogger = NULL;
|
|
}
|
|
if (sLoggersList != NULL && logger != NULL)
|
|
{
|
|
CFIndex where = CFArrayGetFirstIndexOfValue(sLoggersList, CFRangeMake(0, CFArrayGetCount(sLoggersList)), (void const *) logger);
|
|
if (where != -1)
|
|
CFArrayRemoveValueAtIndex(sLoggersList, where);
|
|
}
|
|
pthread_mutex_unlock(&sLoggersListMutex);
|
|
|
|
if (logger != NULL)
|
|
{
|
|
if (logger->workerThread != NULL)
|
|
{
|
|
LoggerStopGrabbingConsole(logger);
|
|
logger->quit = YES;
|
|
pthread_join(logger->workerThread, NULL);
|
|
}
|
|
|
|
CFRelease(logger->bonjourServiceBrowsers);
|
|
CFRelease(logger->bonjourServices);
|
|
free(logger->sendBuffer);
|
|
if (logger->host != NULL)
|
|
CFRelease(logger->host);
|
|
if (logger->bufferFile != NULL)
|
|
CFRelease(logger->bufferFile);
|
|
if (logger->bonjourServiceType != NULL)
|
|
CFRelease(logger->bonjourServiceType);
|
|
if (logger->bonjourServiceName != NULL)
|
|
CFRelease(logger->bonjourServiceName);
|
|
|
|
// to make sure potential errors are caught, set the whole structure
|
|
// to a value that will make code crash if it tries using pointers to it.
|
|
memset(logger, 0x55, sizeof(Logger));
|
|
|
|
free(logger);
|
|
}
|
|
}
|
|
|
|
static void LoggerFlushAllOnExit()
|
|
{
|
|
// this function is automatically configured by NSLogger to flush all connected loggers
|
|
// on exit. this guarantees that the developer sees the last messages issued by the application.
|
|
// it is configured the first time a logger is initialized, so at the time we're being called
|
|
// the loggers list is never NULL
|
|
pthread_mutex_lock(&sLoggersListMutex);
|
|
CFIndex numLoggers = CFArrayGetCount(sLoggersList);
|
|
for (CFIndex i=0; i < numLoggers; i++)
|
|
LoggerFlush((Logger *) CFArrayGetValueAtIndex(sLoggersList, i), NO);
|
|
pthread_mutex_unlock(&sLoggersListMutex);
|
|
}
|
|
|
|
void LoggerFlush(Logger *logger, BOOL waitForConnection)
|
|
{
|
|
// Special case: if nothing has ever been logged, don't bother
|
|
if (logger == NULL && sDefaultLogger == NULL)
|
|
return;
|
|
if (logger == NULL)
|
|
logger = LoggerGetDefaultLogger();
|
|
if (logger != NULL &&
|
|
pthread_self() != logger->workerThread &&
|
|
(logger->connected || logger->bufferFile != NULL || waitForConnection)) // TODO: change this test
|
|
{
|
|
pthread_mutex_lock(&logger->logQueueMutex);
|
|
if (CFArrayGetCount(logger->logQueue) > 0)
|
|
pthread_cond_wait(&logger->logQueueEmpty, &logger->logQueueMutex);
|
|
pthread_mutex_unlock(&logger->logQueueMutex);
|
|
}
|
|
}
|
|
|
|
#if LOGGER_DEBUG
|
|
static void LoggerDbg(CFStringRef format, ...)
|
|
{
|
|
// Internal debugging function
|
|
// (what do you think, that we use the Logger to debug itself ??)
|
|
if (format != NULL)
|
|
{
|
|
@autoreleasepool
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
CFStringRef s = CFStringCreateWithFormatAndArguments(NULL, NULL, format, args);
|
|
va_end(args);
|
|
if (s != NULL)
|
|
{
|
|
CFShow(s);
|
|
CFRelease(s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// -----------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark Main processing
|
|
// -----------------------------------------------------------------------------
|
|
static BOOL LoggerPrepareRunloopSource(Logger *logger, CFRunLoopSourceRef *outRef, void *callback)
|
|
{
|
|
// first call will also create the thread's runloop
|
|
CFRunLoopSourceContext context;
|
|
bzero(&context, sizeof(context));
|
|
context.info = logger;
|
|
context.perform = callback;
|
|
*outRef = CFRunLoopSourceCreate(NULL, 0, &context);
|
|
if (*outRef == NULL)
|
|
{
|
|
// This NSLog is intentional as this failure MUST be logged to console
|
|
NSLog(@"*** NSLogger: worker thread failed creating runloop source");
|
|
return NO;
|
|
}
|
|
CFRunLoopAddSource(CFRunLoopGetCurrent(), *outRef, kCFRunLoopDefaultMode);
|
|
return YES;
|
|
}
|
|
|
|
static void LoggerDisposeRunloopSource(CFRunLoopSourceRef *sourceRef)
|
|
{
|
|
if (*sourceRef != NULL)
|
|
{
|
|
CFRunLoopSourceInvalidate(*sourceRef);
|
|
CFRelease(*sourceRef);
|
|
*sourceRef = NULL;
|
|
}
|
|
}
|
|
|
|
static void *LoggerWorkerThread(Logger *logger)
|
|
{
|
|
LOGGERDBG(CFSTR("Start LoggerWorkerThread"));
|
|
|
|
#if !TARGET_OS_IPHONE
|
|
// Register thread with Garbage Collector on Mac OS X if we're running an OS version that has GC
|
|
void (*registerThreadWithCollector_fn)(void);
|
|
registerThreadWithCollector_fn = (void(*)(void)) dlsym(RTLD_NEXT, "objc_registerThreadWithCollector");
|
|
if (registerThreadWithCollector_fn)
|
|
(*registerThreadWithCollector_fn)();
|
|
#endif
|
|
|
|
// Create the run loop source that signals when messages have been added to the runloop
|
|
// this will directly trigger a WriteMoreData() call, which will or won't write depending
|
|
// on whether we're connected and there's space available in the stream
|
|
if (!LoggerPrepareRunloopSource(logger, &logger->messagePushedSource, &LoggerWriteMoreData))
|
|
{
|
|
// Failing to create the runloop source for pushing messages is a major failure.
|
|
// This NSLog is intentional. We WANT console output in this case
|
|
NSLog(@"*** NSLogger: switching to console logging.");
|
|
logger->options |= kLoggerOption_LogToConsole;
|
|
logger->workerThread = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
// Create the buffering stream if needed
|
|
if (logger->bufferFile != NULL)
|
|
LoggerCreateBufferWriteStream(logger);
|
|
|
|
// Create the runloop source that lets us know when file buffering options change
|
|
LoggerPrepareRunloopSource(logger, &logger->bufferFileChangedSource, &LoggerFileBufferingOptionsChanged);
|
|
|
|
// Create the runloop source that lets us know when remote (host, Bonjour) settings change
|
|
LoggerPrepareRunloopSource(logger, &logger->remoteOptionsChangedSource, &LoggerRemoteSettingsChanged);
|
|
|
|
// Start Reachability (when needed), which determines when we take the next step
|
|
// (once Reachability status is known, we'll decide to either start Bonjour browsing or
|
|
// try connecting to a direct host)
|
|
LoggerStartReachabilityChecking(logger);
|
|
|
|
// Run logging thread until LoggerStop() is called
|
|
NSTimeInterval timeout = 0.10;
|
|
while (!logger->quit)
|
|
{
|
|
int result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, true);
|
|
if (result == kCFRunLoopRunFinished || result == kCFRunLoopRunStopped)
|
|
break;
|
|
if (result == kCFRunLoopRunHandledSource)
|
|
{
|
|
timeout = 0.0;
|
|
continue;
|
|
}
|
|
timeout = fmax(1.0, fmin(0.10, timeout+0.0005));
|
|
}
|
|
|
|
// Cleanup
|
|
LoggerStopBonjourBrowsing(logger);
|
|
LoggerStopReachabilityChecking(logger);
|
|
LoggerStopReconnectTimer(logger);
|
|
|
|
if (logger->logStream != NULL)
|
|
{
|
|
CFWriteStreamSetClient(logger->logStream, 0, NULL, NULL);
|
|
CFWriteStreamClose(logger->logStream);
|
|
CFRelease(logger->logStream);
|
|
logger->logStream = NULL;
|
|
}
|
|
|
|
if (logger->bufferWriteStream == NULL && logger->bufferFile != NULL)
|
|
{
|
|
// If there are messages in the queue and LoggerStop() was called and
|
|
// a buffer file was set just before LoggerStop() was called, flush
|
|
// the log queue to the buffer file
|
|
pthread_mutex_lock(&logger->logQueueMutex);
|
|
CFIndex outstandingMessages = CFArrayGetCount(logger->logQueue);
|
|
pthread_mutex_unlock(&logger->logQueueMutex);
|
|
if (outstandingMessages)
|
|
LoggerCreateBufferWriteStream(logger);
|
|
}
|
|
|
|
if (logger->bufferWriteStream != NULL)
|
|
{
|
|
CFWriteStreamClose(logger->bufferWriteStream);
|
|
CFRelease(logger->bufferWriteStream);
|
|
logger->bufferWriteStream = NULL;
|
|
}
|
|
|
|
LoggerDisposeRunloopSource(&logger->messagePushedSource);
|
|
LoggerDisposeRunloopSource(&logger->bufferFileChangedSource);
|
|
LoggerDisposeRunloopSource(&logger->remoteOptionsChangedSource);
|
|
|
|
// if the client ever tries to log again against us, make sure that logs at least
|
|
// go to console
|
|
logger->options |= kLoggerOption_LogToConsole;
|
|
logger->workerThread = NULL;
|
|
|
|
LOGGERDBG(CFSTR("Stop LoggerWorkerThread"));
|
|
return NULL;
|
|
}
|
|
|
|
static CFStringRef LoggerCreateStringRepresentationFromBinaryData(CFDataRef data)
|
|
{
|
|
CFMutableStringRef s = CFStringCreateMutable(NULL, 0);
|
|
unsigned int offset = 0;
|
|
unsigned int dataLen = (unsigned int)CFDataGetLength(data);
|
|
char buffer[1+6+16*3+1+16+1+1+1];
|
|
buffer[0] = '\0';
|
|
const unsigned char *q = (unsigned char *)CFDataGetBytePtr(data);
|
|
if (dataLen == 1)
|
|
CFStringAppend(s, CFSTR("Raw data, 1 byte:\n"));
|
|
else
|
|
CFStringAppendFormat(s, NULL, CFSTR("Raw data, %u bytes:\n"), dataLen);
|
|
while (dataLen)
|
|
{
|
|
int i, j, b = sprintf(buffer," %04x: ", offset);
|
|
for (i=0; i < 16 && i < (int)dataLen; i++)
|
|
sprintf(&buffer[b+3*i], "%02x ", (int)q[i]);
|
|
for (j=i; j < 16; j++)
|
|
strncat(buffer, " ", 3);
|
|
|
|
b = (int)strlen(buffer);
|
|
buffer[b++] = '\'';
|
|
for (i=0; i < 16 && i < (int)dataLen; i++, q++)
|
|
{
|
|
if (*q >= 32 && *q < 128)
|
|
buffer[b++] = (char)*q;
|
|
else
|
|
buffer[b++] = ' ';
|
|
}
|
|
for (j=i; j < 16; j++)
|
|
buffer[b++] = ' ';
|
|
buffer[b++] = '\'';
|
|
buffer[b++] = '\n';
|
|
buffer[b] = 0;
|
|
|
|
CFStringRef bufferStr = CFStringCreateWithBytesNoCopy(NULL, (const UInt8 *)buffer, (CFIndex)strlen(buffer), kCFStringEncodingISOLatin1, false, kCFAllocatorNull);
|
|
CFStringAppend(s, bufferStr);
|
|
CFRelease(bufferStr);
|
|
|
|
dataLen -= (unsigned int)i;
|
|
offset += (unsigned int)i;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
static void LoggerLogToConsole(CFDataRef data)
|
|
{
|
|
// Decode and log a message to the console. Doing this from the worker thread
|
|
// allow us to serialize logging, which is a benefit that NSLog() doesn't have.
|
|
// Only drawback is that we have to decode our own message, but that is a minor hassle.
|
|
if (data == NULL)
|
|
{
|
|
CFShow(CFSTR("LoggerLogToConsole: data is NULL"));
|
|
return;
|
|
}
|
|
struct timeval timestamp;
|
|
bzero(×tamp, sizeof(timestamp));
|
|
int type = LOGMSG_TYPE_LOG, contentsType = PART_TYPE_STRING;
|
|
int imgWidth=0, imgHeight=0;
|
|
CFStringRef message = NULL;
|
|
CFStringRef thread = NULL;
|
|
|
|
// decode message contents
|
|
uint8_t *p = (uint8_t *)CFDataGetBytePtr(data) + 4;
|
|
uint16_t partCount;
|
|
memcpy(&partCount, p, 2);
|
|
partCount = ntohs(partCount);
|
|
p += 2;
|
|
while (partCount--)
|
|
{
|
|
uint8_t partKey = *p++;
|
|
uint8_t partType = *p++;
|
|
uint32_t partSize;
|
|
if (partType == PART_TYPE_INT16)
|
|
partSize = 2;
|
|
else if (partType == PART_TYPE_INT32)
|
|
partSize = 4;
|
|
else if (partType == PART_TYPE_INT64)
|
|
partSize = 8;
|
|
else
|
|
{
|
|
memcpy(&partSize, p, 4);
|
|
p += 4;
|
|
partSize = ntohl(partSize);
|
|
}
|
|
CFTypeRef part = NULL;
|
|
uint32_t value32 = 0;
|
|
uint64_t value64 = 0;
|
|
if (partSize > 0)
|
|
{
|
|
if (partType == PART_TYPE_STRING)
|
|
{
|
|
// trim whitespace and newline at both ends of the string
|
|
uint8_t *q = p;
|
|
uint32_t l = partSize;
|
|
while (l && (*q == ' ' || *q == '\t' || *q == '\n' || *q == '\r'))
|
|
q++, l--;
|
|
uint8_t *r = q + l - 1;
|
|
while (l && (*r == ' ' || *r == '\t' || *r == '\n' || *r == '\r'))
|
|
r--, l--;
|
|
part = CFStringCreateWithBytesNoCopy(NULL, q, (CFIndex)l, kCFStringEncodingUTF8, false, kCFAllocatorNull);
|
|
}
|
|
else if (partType == PART_TYPE_BINARY)
|
|
{
|
|
part = CFDataCreateWithBytesNoCopy(NULL, p, (CFIndex)partSize, kCFAllocatorNull);
|
|
}
|
|
else if (partType == PART_TYPE_IMAGE)
|
|
{
|
|
// ignore image data, we can't log it to console
|
|
}
|
|
else if (partType == PART_TYPE_INT16)
|
|
{
|
|
value32 = ((uint32_t)p[0]) << 8 | (uint32_t)p[1];
|
|
}
|
|
else if (partType == PART_TYPE_INT32)
|
|
{
|
|
memcpy(&value32, p, 4);
|
|
value32 = ntohl(value32);
|
|
}
|
|
else if (partType == PART_TYPE_INT64)
|
|
{
|
|
memcpy(&value64, p, 8);
|
|
value64 = CFSwapInt64BigToHost(value64);
|
|
}
|
|
p += partSize;
|
|
}
|
|
switch (partKey)
|
|
{
|
|
case PART_KEY_MESSAGE_TYPE:
|
|
type = (int)value32;
|
|
break;
|
|
case PART_KEY_TIMESTAMP_S: // timestamp with seconds-level resolution
|
|
timestamp.tv_sec = (partType == PART_TYPE_INT64) ? (__darwin_time_t)value64 : (__darwin_time_t)value32;
|
|
break;
|
|
case PART_KEY_TIMESTAMP_MS: // millisecond part of the timestamp (optional)
|
|
timestamp.tv_usec = ((partType == PART_TYPE_INT64) ? (__darwin_suseconds_t)value64 : (__darwin_suseconds_t)value32) * 1000;
|
|
break;
|
|
case PART_KEY_TIMESTAMP_US: // microsecond part of the timestamp (optional)
|
|
timestamp.tv_usec = (partType == PART_TYPE_INT64) ? (__darwin_suseconds_t)value64 : (__darwin_suseconds_t)value32;
|
|
break;
|
|
case PART_KEY_THREAD_ID:
|
|
if (thread == NULL) // useless test, we know what we're doing but clang analyzer doesn't...
|
|
{
|
|
if (partType == PART_TYPE_INT32)
|
|
thread = CFStringCreateWithFormat(NULL, NULL, CFSTR("thread 0x%08x"), value32);
|
|
else if (partType == PART_TYPE_INT64)
|
|
thread = CFStringCreateWithFormat(NULL, NULL, CFSTR("thread 0x%qx"), value64);
|
|
else if (partType == PART_TYPE_STRING && part != NULL)
|
|
thread = CFRetain(part);
|
|
}
|
|
break;
|
|
case PART_KEY_MESSAGE:
|
|
if (part != NULL)
|
|
{
|
|
if (partType == PART_TYPE_STRING)
|
|
message = CFRetain(part);
|
|
else if (partType == PART_TYPE_BINARY)
|
|
message = LoggerCreateStringRepresentationFromBinaryData(part);
|
|
}
|
|
contentsType = partType;
|
|
break;
|
|
case PART_KEY_IMAGE_WIDTH:
|
|
imgWidth = (partType == PART_TYPE_INT32 ? (int)value32 : (int)value64);
|
|
break;
|
|
case PART_KEY_IMAGE_HEIGHT:
|
|
imgHeight = (partType == PART_TYPE_INT32 ? (int)value32 : (int)value64);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (part != NULL)
|
|
CFRelease(part);
|
|
}
|
|
|
|
// Prepare the final representation and log to console
|
|
CFMutableStringRef s = CFStringCreateMutable(NULL, 0);
|
|
|
|
char buf[32];
|
|
struct tm t;
|
|
gmtime_r(×tamp.tv_sec, &t);
|
|
strftime(buf, sizeof(buf)-1, "%T", &t);
|
|
CFStringRef ts = CFStringCreateWithBytesNoCopy(
|
|
NULL,
|
|
(const UInt8 *)buf,
|
|
(CFIndex)strlen(buf),
|
|
kCFStringEncodingASCII,
|
|
false,
|
|
kCFAllocatorNull);
|
|
CFStringAppend(s, ts);
|
|
CFRelease(ts);
|
|
|
|
if (contentsType == PART_TYPE_IMAGE)
|
|
message = CFStringCreateWithFormat(NULL, NULL, CFSTR("<image width=%d height=%d>"), imgWidth, imgHeight);
|
|
|
|
buf[0] = 0;
|
|
if (thread != NULL && CFStringGetLength(thread) < 16)
|
|
{
|
|
int n = 16 - (int)CFStringGetLength(thread);
|
|
memset(buf, ' ', (size_t)n);
|
|
buf[n] = 0;
|
|
}
|
|
CFStringAppendFormat(s, NULL, CFSTR(".%04d %s%@ | %@"),
|
|
(int)(timestamp.tv_usec / 1000),
|
|
buf, (thread == NULL) ? CFSTR("") : thread,
|
|
(message != NULL) ? message : CFSTR(""));
|
|
|
|
if (thread != NULL)
|
|
CFRelease(thread);
|
|
if (message != NULL)
|
|
CFRelease(message);
|
|
|
|
if (type == LOGMSG_TYPE_LOG || type == LOGMSG_TYPE_MARK)
|
|
CFShow(s);
|
|
|
|
CFRelease(s);
|
|
}
|
|
|
|
static void LoggerWriteMoreData(Logger *logger)
|
|
{
|
|
uint32_t logToConsole = (logger->options & kLoggerOption_LogToConsole);
|
|
|
|
if (!logger->connected)
|
|
{
|
|
if (logToConsole)
|
|
{
|
|
pthread_mutex_lock(&logger->logQueueMutex);
|
|
while (CFArrayGetCount(logger->logQueue))
|
|
{
|
|
LoggerLogToConsole((CFDataRef)CFArrayGetValueAtIndex(logger->logQueue, 0));
|
|
CFArrayRemoveValueAtIndex(logger->logQueue, 0);
|
|
}
|
|
pthread_mutex_unlock(&logger->logQueueMutex);
|
|
pthread_cond_broadcast(&logger->logQueueEmpty);
|
|
}
|
|
else if (logger->bufferWriteStream != NULL)
|
|
{
|
|
LoggerFlushQueueToBufferStream(logger, NO);
|
|
}
|
|
else if (!(logger->options & kLoggerOption_BufferLogsUntilConnection))
|
|
{
|
|
/* No client connected
|
|
* User don't want to log to console
|
|
* User don't want to log to file
|
|
* and user don't want us to buffer it in memory
|
|
* So let's just sack the whole queue
|
|
*/
|
|
pthread_mutex_lock(&logger->logQueueMutex);
|
|
while (CFArrayGetCount(logger->logQueue))
|
|
{
|
|
CFArrayRemoveValueAtIndex(logger->logQueue, 0);
|
|
}
|
|
pthread_mutex_unlock(&logger->logQueueMutex);
|
|
pthread_cond_broadcast(&logger->logQueueEmpty);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (CFWriteStreamCanAcceptBytes(logger->logStream))
|
|
{
|
|
// prepare archived data with log queue contents, unblock the queue as soon as possible
|
|
CFMutableDataRef sendFirstItem = NULL;
|
|
if (logger->sendBufferUsed == 0)
|
|
{
|
|
// pull more data from the log queue
|
|
if (logger->bufferReadStream != NULL)
|
|
{
|
|
if (!CFReadStreamHasBytesAvailable(logger->bufferReadStream))
|
|
{
|
|
CFReadStreamClose(logger->bufferReadStream);
|
|
CFRelease(logger->bufferReadStream);
|
|
logger->bufferReadStream = NULL;
|
|
LoggerEmptyBufferFile(logger);
|
|
}
|
|
else
|
|
{
|
|
logger->sendBufferUsed = (NSUInteger)CFReadStreamRead(logger->bufferReadStream, logger->sendBuffer, (CFIndex)logger->sendBufferSize);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pthread_mutex_lock(&logger->logQueueMutex);
|
|
while (CFArrayGetCount(logger->logQueue))
|
|
{
|
|
CFDataRef d = (CFDataRef)CFArrayGetValueAtIndex(logger->logQueue, 0);
|
|
CFIndex dsize = CFDataGetLength(d);
|
|
if ((logger->sendBufferUsed + (NSUInteger)dsize) > logger->sendBufferSize)
|
|
break;
|
|
memcpy(logger->sendBuffer + logger->sendBufferUsed, CFDataGetBytePtr(d), (size_t)dsize);
|
|
logger->sendBufferUsed += (NSUInteger)dsize;
|
|
if (logToConsole)
|
|
LoggerLogToConsole(d);
|
|
CFArrayRemoveValueAtIndex(logger->logQueue, 0);
|
|
logger->incompleteSendOfFirstItem = NO;
|
|
}
|
|
pthread_mutex_unlock(&logger->logQueueMutex);
|
|
}
|
|
if (logger->sendBufferUsed == 0)
|
|
{
|
|
// are we done yet?
|
|
pthread_mutex_lock(&logger->logQueueMutex);
|
|
if (CFArrayGetCount(logger->logQueue) == 0)
|
|
{
|
|
pthread_mutex_unlock(&logger->logQueueMutex);
|
|
pthread_cond_broadcast(&logger->logQueueEmpty);
|
|
return;
|
|
}
|
|
|
|
// first item is too big to fit in a single packet, send it separately
|
|
sendFirstItem = (CFMutableDataRef)CFArrayGetValueAtIndex(logger->logQueue, 0);
|
|
logger->incompleteSendOfFirstItem = YES;
|
|
pthread_mutex_unlock(&logger->logQueueMutex);
|
|
logger->sendBufferOffset = 0;
|
|
}
|
|
}
|
|
|
|
// send data over the socket. We try hard to be failsafe and if we have to send
|
|
// data in fragments, we make sure that in case a disconnect occurs we restart
|
|
// sending the whole message(s)
|
|
if (logger->sendBufferUsed != 0)
|
|
{
|
|
CFIndex written = CFWriteStreamWrite(logger->logStream,
|
|
logger->sendBuffer + logger->sendBufferOffset,
|
|
(CFIndex)(logger->sendBufferUsed - logger->sendBufferOffset));
|
|
if (written < 0)
|
|
{
|
|
// We'll get an event if the stream closes on error. Don't discard the data,
|
|
// it will be sent as soon as a connection is re-acquired.
|
|
LOGGERDBG(CFSTR("CFWriteStreamWrite got %d result"),written);
|
|
return;
|
|
}
|
|
if ((logger->sendBufferOffset + (NSUInteger)written) < logger->sendBufferUsed)
|
|
{
|
|
// everything couldn't be sent at once
|
|
logger->sendBufferOffset += (NSUInteger)written;
|
|
}
|
|
else
|
|
{
|
|
logger->sendBufferUsed = 0;
|
|
logger->sendBufferOffset = 0;
|
|
}
|
|
}
|
|
else if (sendFirstItem)
|
|
{
|
|
CFIndex length = CFDataGetLength(sendFirstItem) - (CFIndex)logger->sendBufferOffset;
|
|
CFIndex written = CFWriteStreamWrite(logger->logStream,
|
|
CFDataGetBytePtr(sendFirstItem) + logger->sendBufferOffset,
|
|
length);
|
|
if (written < 0)
|
|
{
|
|
// We'll get an event if the stream closes on error
|
|
return;
|
|
}
|
|
if (written < length)
|
|
{
|
|
// The output pipe is full, and the first item has not been sent completely
|
|
// We need to reduce the remaining data on the first item so it can be taken
|
|
// care of at the next iteration. We take advantage of the fact that each item
|
|
// in the queue is actually a mutable data block
|
|
// @@@ NOTE: IF WE GET DISCONNECTED WHILE DOING THIS, THINGS WILL GO WRONG
|
|
// NEED TO UPDATE THIS LOGIC
|
|
LOGGERDBG(CFSTR("Output pipe is full"));
|
|
CFDataReplaceBytes((CFMutableDataRef)sendFirstItem, CFRangeMake(0, written), NULL, 0);
|
|
return;
|
|
}
|
|
|
|
// we are done sending the first item in the queue, remove it now
|
|
pthread_mutex_lock(&logger->logQueueMutex);
|
|
CFArrayRemoveValueAtIndex(logger->logQueue, 0);
|
|
logger->incompleteSendOfFirstItem = NO;
|
|
pthread_mutex_unlock(&logger->logQueueMutex);
|
|
logger->sendBufferOffset = 0;
|
|
}
|
|
|
|
pthread_mutex_lock(&logger->logQueueMutex);
|
|
CFIndex remainingMsgs = CFArrayGetCount(logger->logQueue);
|
|
pthread_mutex_unlock(&logger->logQueueMutex);
|
|
if (remainingMsgs == 0)
|
|
pthread_cond_broadcast(&logger->logQueueEmpty);
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark Console logs redirection support
|
|
// -----------------------------------------------------------------------------
|
|
static void LoggerLogFromConsole(NSString *tag, int fd, int outfd)
|
|
{
|
|
const int BUFSIZE = 1000;
|
|
UInt8 buf[BUFSIZE];
|
|
ssize_t bytes_read = 0;
|
|
while ((bytes_read = read(fd, buf, BUFSIZE-1)) > 0)
|
|
{
|
|
// output received data to the original fd
|
|
if (outfd != -1)
|
|
write(outfd, buf, (size_t)bytes_read);
|
|
|
|
if (buf[bytes_read-1] == '\n')
|
|
--bytes_read;
|
|
|
|
CFStringRef messageString = CFStringCreateWithBytes(NULL, buf, bytes_read, kCFStringEncodingUTF8, false);
|
|
if (messageString != NULL)
|
|
{
|
|
CFArrayRef array = CFStringCreateArrayBySeparatingStrings(NULL, messageString, CFSTR("\n"));
|
|
if (array != NULL)
|
|
{
|
|
pthread_mutex_lock(&consoleGrabbersMutex);
|
|
|
|
CFIndex n = CFArrayGetCount(array);
|
|
for (CFIndex m = 0; m < n; m++)
|
|
{
|
|
CFStringRef msg = (CFStringRef)CFArrayGetValueAtIndex(array, m);
|
|
for (unsigned i = 0; i < consoleGrabbersListLength; i++)
|
|
{
|
|
if (consoleGrabbersList[i] != NULL)
|
|
LogMessageTo(consoleGrabbersList[i], tag, 0, @"%@", msg);
|
|
}
|
|
}
|
|
|
|
pthread_mutex_unlock(&consoleGrabbersMutex);
|
|
|
|
CFRelease(array);
|
|
}
|
|
CFRelease(messageString);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void *LoggerConsoleGrabThread(void *context)
|
|
{
|
|
#pragma unused (context)
|
|
|
|
int fdout = sConsolePipes[0];
|
|
fcntl(fdout, F_SETFL, fcntl(fdout, F_GETFL, 0) | O_NONBLOCK);
|
|
|
|
int fderr = sConsolePipes[2];
|
|
fcntl(fderr, F_SETFL, fcntl(fderr, F_GETFL, 0) | O_NONBLOCK);
|
|
|
|
while (numActiveConsoleGrabbers != 0)
|
|
{
|
|
fd_set set;
|
|
FD_ZERO(&set);
|
|
FD_SET(fdout, &set);
|
|
FD_SET(fderr, &set);
|
|
|
|
int ret = select(fderr + 1, &set, NULL, NULL, NULL);
|
|
|
|
if (ret <= 0)
|
|
{
|
|
// ==0: time expired without activity
|
|
// < 0: error occurred
|
|
break;
|
|
}
|
|
|
|
if (FD_ISSET(fdout, &set))
|
|
LoggerLogFromConsole(@"stdout", fdout, sSTDOUT);
|
|
if (FD_ISSET(fderr, &set ))
|
|
LoggerLogFromConsole(@"stderr", fderr, sSTDERR);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void LoggerStartConsoleRedirection()
|
|
{
|
|
// keep the original pipes so we can still forward everything
|
|
// (i.e. to the running IDE that needs to display or interpret console messages)
|
|
// and remember the SIGPIPE settings, as we are going to clear them to prevent
|
|
// the app from exiting when we close the pipes
|
|
if (sSTDOUT == -1)
|
|
{
|
|
sSTDOUThadSIGPIPE = fcntl(STDOUT_FILENO, F_GETNOSIGPIPE);
|
|
sSTDOUT = dup(STDOUT_FILENO);
|
|
sSTDERRhadSIGPIPE = fcntl(STDERR_FILENO, F_GETNOSIGPIPE);
|
|
sSTDERR = dup(STDERR_FILENO);
|
|
}
|
|
|
|
// create the pipes
|
|
if (sConsolePipes[0] == -1)
|
|
{
|
|
if (pipe(sConsolePipes) != -1)
|
|
{
|
|
fcntl(sConsolePipes[0], F_SETNOSIGPIPE, 1);
|
|
fcntl(sConsolePipes[1], F_SETNOSIGPIPE, 1);
|
|
dup2(sConsolePipes[1], STDOUT_FILENO);
|
|
}
|
|
}
|
|
|
|
if (sConsolePipes[2] == -1)
|
|
{
|
|
if (pipe(&sConsolePipes[2]) != -1)
|
|
{
|
|
fcntl(sConsolePipes[0], F_SETNOSIGPIPE, 1);
|
|
fcntl(sConsolePipes[1], F_SETNOSIGPIPE, 1);
|
|
dup2(sConsolePipes[3], STDERR_FILENO);
|
|
}
|
|
}
|
|
|
|
pthread_create(&consoleGrabThread, NULL, &LoggerConsoleGrabThread, NULL);
|
|
}
|
|
|
|
static void LoggerStopConsoleRedirection()
|
|
{
|
|
// close the pipes - will force exiting the console logger thread
|
|
// assume the console grabber mutex has been acquired
|
|
dup2(sSTDOUT, STDOUT_FILENO);
|
|
dup2(sSTDERR, STDERR_FILENO);
|
|
|
|
close(sSTDOUT);
|
|
close(sSTDERR);
|
|
|
|
// restore sigpipe flag on standard streams
|
|
fcntl(STDOUT_FILENO, F_SETNOSIGPIPE, &sSTDOUThadSIGPIPE);
|
|
fcntl(STDERR_FILENO, F_SETNOSIGPIPE, &sSTDERRhadSIGPIPE);
|
|
|
|
// close pipes, this will trigger an error in select() and a console grab thread exit
|
|
if (sConsolePipes[0] != -1)
|
|
{
|
|
close(sConsolePipes[0]);
|
|
close(sConsolePipes[1]);
|
|
sConsolePipes[0] = -1;
|
|
}
|
|
if (sConsolePipes[2] != -1)
|
|
{
|
|
close(sConsolePipes[2]);
|
|
close(sConsolePipes[1]);
|
|
}
|
|
sConsolePipes[0] = sConsolePipes[1] = sConsolePipes[2] = sConsolePipes[3] = -1;
|
|
|
|
pthread_join(consoleGrabThread, NULL);
|
|
}
|
|
|
|
static void LoggerStartGrabbingConsole(Logger *logger)
|
|
{
|
|
if (!(logger->options & kLoggerOption_CaptureSystemConsole))
|
|
return;
|
|
|
|
pthread_mutex_lock(&consoleGrabbersMutex);
|
|
|
|
Boolean added = FALSE;
|
|
for (unsigned i = 0; i < numActiveConsoleGrabbers; i++)
|
|
{
|
|
if (consoleGrabbersList[i] == NULL)
|
|
{
|
|
consoleGrabbersList[i] = logger;
|
|
numActiveConsoleGrabbers++;
|
|
added = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (!added)
|
|
{
|
|
consoleGrabbersList = realloc(consoleGrabbersList, ++consoleGrabbersListLength * sizeof(Logger *));
|
|
consoleGrabbersList[numActiveConsoleGrabbers++] = logger;
|
|
}
|
|
|
|
LoggerStartConsoleRedirection(); // Start redirection if necessary
|
|
|
|
pthread_mutex_unlock( &consoleGrabbersMutex );
|
|
}
|
|
|
|
static void LoggerStopGrabbingConsole(Logger *logger)
|
|
{
|
|
if (numActiveConsoleGrabbers == 0)
|
|
return;
|
|
|
|
pthread_mutex_lock(&consoleGrabbersMutex);
|
|
|
|
for (unsigned grabberIndex = 0; grabberIndex < consoleGrabbersListLength; grabberIndex++)
|
|
{
|
|
if (consoleGrabbersList[grabberIndex] == logger)
|
|
{
|
|
consoleGrabbersList[grabberIndex] = NULL;
|
|
if (--numActiveConsoleGrabbers == 0)
|
|
{
|
|
consoleGrabbersListLength = 0;
|
|
free(consoleGrabbersList);
|
|
consoleGrabbersList = NULL;
|
|
LoggerStopConsoleRedirection();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
pthread_mutex_unlock(&consoleGrabbersMutex);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark File buffering functions
|
|
// -----------------------------------------------------------------------------
|
|
static void LoggerCreateBufferWriteStream(Logger *logger)
|
|
{
|
|
LOGGERDBG(CFSTR("LoggerCreateBufferWriteStream to file %@"), logger->bufferFile);
|
|
CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, logger->bufferFile, kCFURLPOSIXPathStyle, false);
|
|
if (fileURL != NULL)
|
|
{
|
|
// Create write stream to file
|
|
logger->bufferWriteStream = CFWriteStreamCreateWithFile(NULL, fileURL);
|
|
CFRelease(fileURL);
|
|
if (logger->bufferWriteStream != NULL)
|
|
{
|
|
// Set flag to append new data to buffer file
|
|
CFWriteStreamSetProperty(logger->bufferWriteStream, kCFStreamPropertyAppendToFile, kCFBooleanTrue);
|
|
|
|
// Open the buffer stream for writing
|
|
if (!CFWriteStreamOpen(logger->bufferWriteStream))
|
|
{
|
|
CFRelease(logger->bufferWriteStream);
|
|
logger->bufferWriteStream = NULL;
|
|
}
|
|
else
|
|
{
|
|
// Write client info and flush the queue contents to buffer file
|
|
LoggerPushClientInfoToFrontOfQueue(logger);
|
|
LoggerFlushQueueToBufferStream(logger, YES);
|
|
}
|
|
}
|
|
}
|
|
if (logger->bufferWriteStream == NULL)
|
|
{
|
|
CFShow(CFSTR("NSLogger Warning: failed opening buffer file for writing:"));
|
|
CFShow(logger->bufferFile);
|
|
}
|
|
}
|
|
|
|
static void LoggerCreateBufferReadStream(Logger *logger)
|
|
{
|
|
LOGGERDBG(CFSTR("LoggerCreateBufferReadStream from file %@"), logger->bufferFile);
|
|
CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, logger->bufferFile, kCFURLPOSIXPathStyle, false);
|
|
if (fileURL != NULL)
|
|
{
|
|
// Create read stream from file
|
|
logger->bufferReadStream = CFReadStreamCreateWithFile(NULL, fileURL);
|
|
CFRelease(fileURL);
|
|
if (logger->bufferReadStream != NULL)
|
|
{
|
|
if (!CFReadStreamOpen(logger->bufferReadStream))
|
|
{
|
|
CFRelease(logger->bufferReadStream);
|
|
logger->bufferReadStream = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void LoggerEmptyBufferFile(Logger *logger)
|
|
{
|
|
// completely remove the buffer file from storage
|
|
LOGGERDBG(CFSTR("LoggerEmptyBufferFile %@"), logger->bufferFile);
|
|
if (logger->bufferFile != NULL)
|
|
{
|
|
CFIndex bufferSize = 1 + CFStringGetLength(logger->bufferFile) * 3;
|
|
char *buffer = (char *)malloc((size_t)bufferSize);
|
|
if (buffer != NULL)
|
|
{
|
|
if (CFStringGetFileSystemRepresentation(logger->bufferFile, buffer, bufferSize))
|
|
{
|
|
// remove file
|
|
unlink(buffer);
|
|
}
|
|
free(buffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void LoggerFileBufferingOptionsChanged(Logger *logger)
|
|
{
|
|
// File buffering options changed (callback called on logger thread):
|
|
// - close the current buffer file stream, if any
|
|
// - create a new one, if needed
|
|
LOGGERDBG(CFSTR("LoggerFileBufferingOptionsChanged bufferFile=%@"), logger->bufferFile);
|
|
if (logger->bufferWriteStream != NULL)
|
|
{
|
|
CFWriteStreamClose(logger->bufferWriteStream);
|
|
CFRelease(logger->bufferWriteStream);
|
|
logger->bufferWriteStream = NULL;
|
|
}
|
|
if (logger->bufferFile != NULL)
|
|
LoggerCreateBufferWriteStream(logger);
|
|
}
|
|
|
|
static void LoggerFlushQueueToBufferStream(Logger *logger, BOOL firstEntryIsClientInfo)
|
|
{
|
|
LOGGERDBG(CFSTR("LoggerFlushQueueToBufferStream"));
|
|
pthread_mutex_lock(&logger->logQueueMutex);
|
|
if (logger->incompleteSendOfFirstItem)
|
|
{
|
|
// drop anything being sent
|
|
logger->sendBufferUsed = 0;
|
|
logger->sendBufferOffset = 0;
|
|
}
|
|
logger->incompleteSendOfFirstItem = NO;
|
|
|
|
// Write outstanding messages to the buffer file (streams don't detect disconnection
|
|
// until the next write, where we could lose one or more messages)
|
|
if (!firstEntryIsClientInfo && logger->sendBufferUsed)
|
|
CFWriteStreamWrite(logger->bufferWriteStream, logger->sendBuffer + logger->sendBufferOffset, (CFIndex)(logger->sendBufferUsed - logger->sendBufferOffset));
|
|
|
|
int n = 0;
|
|
while (CFArrayGetCount(logger->logQueue))
|
|
{
|
|
CFDataRef data = CFArrayGetValueAtIndex(logger->logQueue, 0);
|
|
CFIndex dataLength = CFDataGetLength(data);
|
|
CFIndex written = CFWriteStreamWrite(logger->bufferWriteStream, CFDataGetBytePtr(data), dataLength);
|
|
if (written != dataLength)
|
|
{
|
|
// couldn't write all data to file, maybe storage run out of space?
|
|
CFShow(CFSTR("NSLogger Error: failed flushing the whole queue to buffer file:"));
|
|
CFShow(logger->bufferFile);
|
|
break;
|
|
}
|
|
CFArrayRemoveValueAtIndex(logger->logQueue, 0);
|
|
if (n == 0 && firstEntryIsClientInfo && logger->sendBufferUsed)
|
|
{
|
|
// try hard: write any outstanding messages to the buffer file, after the client info
|
|
CFWriteStreamWrite(logger->bufferWriteStream, logger->sendBuffer + logger->sendBufferOffset, (CFIndex)(logger->sendBufferUsed - logger->sendBufferOffset));
|
|
}
|
|
n++;
|
|
}
|
|
logger->sendBufferUsed = 0;
|
|
logger->sendBufferOffset = 0;
|
|
pthread_mutex_unlock(&logger->logQueueMutex);
|
|
pthread_cond_broadcast(&logger->logQueueEmpty);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark Bonjour browsing
|
|
// -----------------------------------------------------------------------------
|
|
static void LoggerStartBonjourBrowsing(Logger *logger)
|
|
{
|
|
if (!logger->targetReachable ||
|
|
logger->bonjourDomainBrowser != NULL ||
|
|
!(logger->options & kLoggerOption_BrowseBonjour))
|
|
return;
|
|
|
|
LOGGERDBG(CFSTR("LoggerStartBonjourBrowsing"));
|
|
|
|
if (logger->options & kLoggerOption_BrowseOnlyLocalDomain)
|
|
{
|
|
LOGGERDBG(CFSTR("Logger configured to search only the local domain, searching for services on: local."));
|
|
if (!LoggerBrowseBonjourForServices(logger, CFSTR("local.")) && logger->host == NULL)
|
|
{
|
|
LOGGERDBG(CFSTR("*** Logger: could not browse for services in domain local., no remote host configured: reverting to console logging. ***"));
|
|
logger->options |= kLoggerOption_LogToConsole;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOGGERDBG(CFSTR("Logger configured to search all domains, browsing for domains first"));
|
|
CFNetServiceClientContext context = {0, (void *)logger, NULL, NULL, NULL};
|
|
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
|
|
logger->bonjourDomainBrowser = CFNetServiceBrowserCreate(NULL, &LoggerServiceBrowserCallBack, &context);
|
|
CFNetServiceBrowserScheduleWithRunLoop(logger->bonjourDomainBrowser, runLoop, kCFRunLoopCommonModes);
|
|
if (!CFNetServiceBrowserSearchForDomains(logger->bonjourDomainBrowser, false, NULL))
|
|
{
|
|
// An error occurred, revert to console logging if there is no remote host
|
|
LOGGERDBG(CFSTR("*** Logger: could not browse for domains, reverting to console logging. ***"));
|
|
CFNetServiceBrowserUnscheduleFromRunLoop(logger->bonjourDomainBrowser, runLoop, kCFRunLoopCommonModes);
|
|
CFRelease(logger->bonjourDomainBrowser);
|
|
logger->bonjourDomainBrowser = NULL;
|
|
if (logger->host == NULL)
|
|
logger->options |= kLoggerOption_LogToConsole;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void LoggerStopBonjourBrowsing(Logger *logger)
|
|
{
|
|
LOGGERDBG(CFSTR("LoggerStopBonjourBrowsing"));
|
|
|
|
// stop browsing for domains
|
|
if (logger->bonjourDomainBrowser != NULL)
|
|
{
|
|
CFNetServiceBrowserStopSearch(logger->bonjourDomainBrowser, NULL);
|
|
CFNetServiceBrowserUnscheduleFromRunLoop(logger->bonjourDomainBrowser, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
|
|
CFNetServiceBrowserInvalidate(logger->bonjourDomainBrowser);
|
|
CFRelease(logger->bonjourDomainBrowser);
|
|
logger->bonjourDomainBrowser = NULL;
|
|
}
|
|
|
|
// stop browsing for services
|
|
CFIndex idx;
|
|
for (idx = 0; idx < CFArrayGetCount(logger->bonjourServiceBrowsers); idx++)
|
|
{
|
|
CFNetServiceBrowserRef browser = (CFNetServiceBrowserRef)CFArrayGetValueAtIndex(logger->bonjourServiceBrowsers, idx);
|
|
CFNetServiceBrowserStopSearch(browser, NULL);
|
|
CFNetServiceBrowserUnscheduleFromRunLoop(browser, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
|
|
CFNetServiceBrowserInvalidate(browser);
|
|
}
|
|
CFArrayRemoveAllValues(logger->bonjourServiceBrowsers);
|
|
|
|
// Forget all services
|
|
CFArrayRemoveAllValues(logger->bonjourServices);
|
|
}
|
|
|
|
static BOOL LoggerBrowseBonjourForServices(Logger *logger, CFStringRef domainName)
|
|
{
|
|
BOOL result = NO;
|
|
CFNetServiceClientContext context = {0, (void *)logger, NULL, NULL, NULL};
|
|
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
|
|
|
|
CFNetServiceBrowserRef browser = CFNetServiceBrowserCreate(NULL, (CFNetServiceBrowserClientCallBack)&LoggerServiceBrowserCallBack, &context);
|
|
CFNetServiceBrowserScheduleWithRunLoop(browser, runLoop, kCFRunLoopCommonModes);
|
|
CFStreamError error;
|
|
|
|
// try to use the user-specfied service type if any, fallback on our
|
|
// default service type
|
|
CFStringRef serviceType = logger->bonjourServiceType;
|
|
if (serviceType == NULL)
|
|
{
|
|
if (logger->options & kLoggerOption_UseSSL)
|
|
serviceType = LOGGER_SERVICE_TYPE_SSL;
|
|
else
|
|
serviceType = LOGGER_SERVICE_TYPE;
|
|
}
|
|
if (!CFNetServiceBrowserSearchForServices(browser, domainName, serviceType, &error))
|
|
{
|
|
LOGGERDBG(CFSTR("Logger can't start search on domain: %@ (error %d)"), domainName, error.error);
|
|
CFNetServiceBrowserUnscheduleFromRunLoop(browser, runLoop, kCFRunLoopCommonModes);
|
|
CFNetServiceBrowserInvalidate(browser);
|
|
}
|
|
else
|
|
{
|
|
LOGGERDBG(CFSTR("Logger started search for services of type %@ in domain %@"), serviceType, domainName);
|
|
CFArrayAppendValue(logger->bonjourServiceBrowsers, browser);
|
|
result = YES;
|
|
}
|
|
CFRelease(browser);
|
|
return result;
|
|
}
|
|
|
|
static void LoggerServiceBrowserCallBack (CFNetServiceBrowserRef browser,
|
|
CFOptionFlags flags,
|
|
CFTypeRef domainOrService,
|
|
CFStreamError* error,
|
|
void* info)
|
|
{
|
|
#pragma unused (browser)
|
|
#pragma unused (error)
|
|
LOGGERDBG(CFSTR("LoggerServiceBrowserCallback browser=%@ flags=0x%04x domainOrService=%@ error=%d"), browser, flags, domainOrService, error==NULL ? 0 : error->error);
|
|
|
|
Logger *logger = (Logger *)info;
|
|
assert(logger != NULL);
|
|
|
|
if (flags & kCFNetServiceFlagRemove)
|
|
{
|
|
if (!(flags & kCFNetServiceFlagIsDomain))
|
|
{
|
|
CFNetServiceRef service = (CFNetServiceRef)domainOrService;
|
|
CFIndex idx;
|
|
for (idx = 0; idx < CFArrayGetCount(logger->bonjourServices); idx++)
|
|
{
|
|
if (CFArrayGetValueAtIndex(logger->bonjourServices, idx) == service)
|
|
{
|
|
CFNetServiceUnscheduleFromRunLoop(service, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
|
|
CFNetServiceClientContext context = {0, NULL, NULL, NULL, NULL};
|
|
CFNetServiceSetClient(service, NULL, &context);
|
|
CFNetServiceCancel(service);
|
|
CFArrayRemoveValueAtIndex(logger->bonjourServices, idx);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (flags & kCFNetServiceFlagIsDomain)
|
|
{
|
|
// start searching for services in this domain
|
|
LoggerBrowseBonjourForServices(logger, (CFStringRef)domainOrService);
|
|
}
|
|
else
|
|
{
|
|
// a service has been found
|
|
LOGGERDBG(CFSTR("Logger found service: %@"), domainOrService);
|
|
CFNetServiceRef service = (CFNetServiceRef)domainOrService;
|
|
if (service != NULL)
|
|
{
|
|
// if the user has specified that Logger shall only connect to the specified
|
|
// Bonjour service name, check it now. This makes things easier in a teamwork
|
|
// environment where multiple instances of NSLogger viewer may run on the
|
|
// same network
|
|
if (logger->bonjourServiceName != NULL)
|
|
{
|
|
LOGGERDBG(CFSTR("-> looking for services of name %@"), logger->bonjourServiceName);
|
|
CFStringRef name = CFNetServiceGetName(service);
|
|
if (name == NULL || kCFCompareEqualTo != CFStringCompare(name, logger->bonjourServiceName, kCFCompareCaseInsensitive | kCFCompareDiacriticInsensitive))
|
|
{
|
|
LOGGERDBG(CFSTR("-> service name %@ does not match requested service name, ignoring."), name, logger->bonjourServiceName);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If the desktop viewer we found requested that only clients looking for its name can connect,
|
|
// honor the request and do not connect. This helps with teams having multiple devices and multiple
|
|
// desktops with NSLogger installed to avoid unwanted logs coming to a specific viewer
|
|
// To indicate that the desktop only wants clients that are looking for its specific name,
|
|
// the desktop sets the TXT record to be a dictionary containing the @"filterClients" key with value @"1"
|
|
CFDataRef txtData = CFNetServiceGetTXTData(service);
|
|
if (txtData != NULL)
|
|
{
|
|
CFDictionaryRef txtDict = CFNetServiceCreateDictionaryWithTXTData(NULL, txtData);
|
|
if (txtDict != NULL)
|
|
{
|
|
const void *value = CFDictionaryGetValue(txtDict, CFSTR("filterClients"));
|
|
Boolean mismatch = (value != NULL &&
|
|
CFGetTypeID((CFTypeRef)value) == CFStringGetTypeID() &&
|
|
CFStringCompare((CFStringRef)value, CFSTR("1"), 0) != kCFCompareEqualTo);
|
|
CFRelease(txtDict);
|
|
if (mismatch)
|
|
{
|
|
LOGGERDBG(CFSTR("-> service %@ requested that only clients looking for it do connect."), name, logger->bonjourServiceName);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CFArrayAppendValue(logger->bonjourServices, service);
|
|
LoggerTryConnect(logger);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark Reachability & Connectivity Management
|
|
// -----------------------------------------------------------------------------
|
|
static void LoggerRemoteSettingsChanged(Logger *logger)
|
|
{
|
|
// this is a callback for a runloop source, called on the logger thread
|
|
|
|
// Always terminate any ongoing connection first
|
|
LoggerWriteStreamTerminated(logger);
|
|
|
|
if (logger->host == NULL && !(logger->options & kLoggerOption_BrowseBonjour))
|
|
{
|
|
// developer doesn't want any network connection
|
|
LoggerStopBonjourBrowsing(logger);
|
|
LoggerStopReconnectTimer(logger);
|
|
LoggerStopReachabilityChecking(logger);
|
|
}
|
|
else
|
|
{
|
|
// we may already have Reachability or Bonjour browsing running,
|
|
// the calls do nothing if they are not needed
|
|
LoggerStartReachabilityChecking(logger);
|
|
if (logger->targetReachable)
|
|
{
|
|
if (logger->options & kLoggerOption_BrowseBonjour)
|
|
LoggerStartBonjourBrowsing(logger);
|
|
else
|
|
LoggerStopBonjourBrowsing(logger);
|
|
}
|
|
LoggerTryConnect(logger);
|
|
}
|
|
}
|
|
|
|
static void LoggerStartReachabilityChecking(Logger *logger)
|
|
{
|
|
if (logger->reachability == NULL)
|
|
{
|
|
if (logger->host != NULL)
|
|
{
|
|
// reachability targeted to the configured host
|
|
LOGGERDBG(CFSTR("Starting SCNetworkReachability to wait for host %@ to be reachable"), logger->host);
|
|
CFIndex length = CFStringGetLength(logger->host) * 3;
|
|
char *buffer = (char *)malloc((size_t)length + 1);
|
|
CFStringGetBytes(logger->host, CFRangeMake(0, CFStringGetLength(logger->host)), kCFStringEncodingUTF8, '?', false, (UInt8 *)buffer, length, &length);
|
|
buffer[length] = 0;
|
|
|
|
logger->reachability = SCNetworkReachabilityCreateWithName(NULL, buffer);
|
|
|
|
free(buffer);
|
|
}
|
|
else
|
|
{
|
|
// reachability for generic connection to the internet
|
|
LOGGERDBG(CFSTR("Starting SCNetworkReachability to wait for internet to be reachable"), logger->host);
|
|
struct sockaddr_in addr;
|
|
bzero(&addr, sizeof(addr));
|
|
addr.sin_len = (__uint8_t) sizeof(addr);
|
|
addr.sin_family = AF_INET;
|
|
|
|
logger->reachability = SCNetworkReachabilityCreateWithAddress(NULL, (const struct sockaddr *)&addr);
|
|
}
|
|
|
|
SCNetworkReachabilityContext context = {0, logger, NULL, NULL, NULL};
|
|
SCNetworkReachabilitySetCallback(logger->reachability, &LoggerReachabilityCallBack, &context);
|
|
SCNetworkReachabilityScheduleWithRunLoop(logger->reachability, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
|
|
|
|
// arm the callback
|
|
if (SCNetworkReachabilityGetFlags(logger->reachability, &logger->reachabilityFlags))
|
|
LoggerReachabilityCallBack(logger->reachability, logger->reachabilityFlags, logger);
|
|
}
|
|
}
|
|
|
|
static void LoggerStopReachabilityChecking(Logger *logger)
|
|
{
|
|
if (logger->reachability != NULL)
|
|
{
|
|
LOGGERDBG(CFSTR("Stopping SCNetworkReachability"));
|
|
SCNetworkReachabilityUnscheduleFromRunLoop(logger->reachability, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
|
|
CFRelease((CFTypeRef)logger->reachability);
|
|
logger->reachability = NULL;
|
|
}
|
|
LoggerStopReconnectTimer(logger);
|
|
logger->targetReachable = NO;
|
|
}
|
|
|
|
static void LoggerReachabilityCallBack(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
|
|
{
|
|
#pragma unused (target)
|
|
Logger *logger = (Logger *)info;
|
|
|
|
LOGGERDBG(CFSTR("LoggerReachabilityCallBack called with flags=0x%08lx"), flags);
|
|
|
|
SCNetworkReachabilityFlags oldFlags = logger->reachabilityFlags;
|
|
logger->reachabilityFlags = flags;
|
|
|
|
if (flags & kSCNetworkReachabilityFlagsReachable)
|
|
{
|
|
// target host or internet became reachable
|
|
LOGGERDBG(CFSTR("-> target became reachable"));
|
|
logger->targetReachable = YES;
|
|
|
|
// in the event a network transition occurred without network loss (i.e. WiFi -> 3G),
|
|
// preemptively disconnect. In many cases, if the network stays up, we will never receive
|
|
// a disconnection (possibly due to SSH ?)
|
|
if (flags != oldFlags && logger->logStream != NULL)
|
|
LoggerWriteStreamTerminated(logger);
|
|
else
|
|
LoggerTryConnect(logger); // will start Bonjour browsing if needed
|
|
}
|
|
else if (logger->connected || logger->logStream != NULL)
|
|
{
|
|
// lost internet connecton. Force a disconnect, we'll wait for the connection to become
|
|
// available again
|
|
LOGGERDBG(CFSTR("-> target became unreachable"));
|
|
logger->targetReachable = NO;
|
|
if (flags != oldFlags && logger->logStream != NULL)
|
|
LoggerWriteStreamTerminated(logger);
|
|
LoggerStopBonjourBrowsing(logger);
|
|
LoggerStopReconnectTimer(logger);
|
|
}
|
|
}
|
|
|
|
static void LoggerStartReconnectTimer(Logger *logger)
|
|
{
|
|
// start a timer that will try to reconnect every 5 seconds
|
|
if (logger->reconnectTimer == NULL && (logger->host != NULL || (logger->options & kLoggerOption_BrowseBonjour)))
|
|
{
|
|
LOGGERDBG(CFSTR("Starting the reconnect timer"));
|
|
CFRunLoopTimerContext timerCtx = {
|
|
.version = 0,
|
|
.info = logger,
|
|
.retain = NULL,
|
|
.release = NULL,
|
|
.copyDescription = NULL
|
|
};
|
|
logger->reconnectTimer = CFRunLoopTimerCreate(NULL,
|
|
CFAbsoluteTimeGetCurrent() + 5,
|
|
5, // reconnect interval
|
|
0,
|
|
0,
|
|
&LoggerTimedReconnectCallback,
|
|
&timerCtx);
|
|
if (logger->reconnectTimer != NULL)
|
|
{
|
|
LOGGERDBG(CFSTR("Starting the TimedReconnect timer to regularly retry the connection"));
|
|
CFRunLoopAddTimer(CFRunLoopGetCurrent(), logger->reconnectTimer, kCFRunLoopCommonModes);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void LoggerStopReconnectTimer(Logger *logger)
|
|
{
|
|
if (logger->reconnectTimer != NULL)
|
|
{
|
|
LOGGERDBG(CFSTR("Stopping the reconnect timer"));
|
|
CFRunLoopTimerInvalidate(logger->reconnectTimer);
|
|
CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), logger->reconnectTimer, kCFRunLoopCommonModes);
|
|
CFRelease(logger->reconnectTimer);
|
|
logger->reconnectTimer = NULL;
|
|
}
|
|
}
|
|
|
|
static void LoggerTimedReconnectCallback(CFRunLoopTimerRef timer, void *info)
|
|
{
|
|
#pragma unused (timer)
|
|
Logger *logger = (Logger *)info;
|
|
assert(logger != NULL);
|
|
LOGGERDBG(CFSTR("LoggerTimedReconnectCallback"));
|
|
if (logger->logStream == NULL)
|
|
{
|
|
LOGGERDBG(CFSTR("-> trying to reconnect to host %@"), logger->host);
|
|
LoggerTryConnect(logger);
|
|
}
|
|
else
|
|
{
|
|
LOGGERDBG(CFSTR("-> timer not needed anymore, removing it form runloop"));
|
|
LoggerStopReconnectTimer(logger);
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark Stream management
|
|
// -----------------------------------------------------------------------------
|
|
static BOOL LoggerConfigureAndOpenStream(Logger *logger)
|
|
{
|
|
// configure and open stream
|
|
LOGGERDBG(CFSTR("LoggerConfigureAndOpenStream configuring and opening log stream"));
|
|
CFStreamClientContext context = {0, (void *)logger, NULL, NULL, NULL};
|
|
if (CFWriteStreamSetClient(logger->logStream,
|
|
(kCFStreamEventOpenCompleted |
|
|
kCFStreamEventCanAcceptBytes |
|
|
kCFStreamEventErrorOccurred |
|
|
kCFStreamEventEndEncountered),
|
|
&LoggerWriteStreamCallback,
|
|
&context))
|
|
{
|
|
if (logger->options & kLoggerOption_UseSSL)
|
|
{
|
|
// Configure stream to require a SSL connection
|
|
LOGGERDBG(CFSTR("-> configuring SSL"));
|
|
const void *SSLKeys[] = {
|
|
kCFStreamSSLLevel,
|
|
kCFStreamSSLValidatesCertificateChain,
|
|
kCFStreamSSLIsServer,
|
|
kCFStreamSSLPeerName
|
|
};
|
|
const void *SSLValues[] = {
|
|
kCFStreamSocketSecurityLevelNegotiatedSSL,
|
|
kCFBooleanFalse, // no certificate chain validation (we use a self-signed certificate)
|
|
kCFBooleanFalse, // not a server
|
|
kCFNull
|
|
};
|
|
|
|
#if TARGET_OS_IPHONE
|
|
// workaround for TLS in iOS 5 as per TN2287
|
|
// see http://developer.apple.com/library/ios/#technotes/tn2287/_index.html#//apple_ref/doc/uid/DTS40011309
|
|
// if we are running iOS 5 or later, use a special mode that allows the stack to downgrade gracefully
|
|
#if ALLOW_COCOA_USE
|
|
@autoreleasepool {
|
|
NSString *versionString = [[UIDevice currentDevice] systemVersion];
|
|
if ([versionString compare:@"5.0" options:NSNumericSearch] != NSOrderedAscending)
|
|
SSLValues[0] = CFSTR("kCFStreamSocketSecurityLevelTLSv1_0SSLv3");
|
|
}
|
|
#else
|
|
// we can't find out, assume we _may_ be on iOS 5 but can't be certain
|
|
// go for SSLv3 which works without the TLS 1.2 / 1.1 / 1.0 downgrade issue
|
|
SSLValues[0] = kCFStreamSocketSecurityLevelSSLv3;
|
|
#endif
|
|
#endif
|
|
|
|
CFDictionaryRef SSLDict = CFDictionaryCreate(NULL, SSLKeys, SSLValues, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
|
CFWriteStreamSetProperty(logger->logStream, kCFStreamPropertySSLSettings, SSLDict);
|
|
CFRelease(SSLDict);
|
|
}
|
|
|
|
CFWriteStreamScheduleWithRunLoop(logger->logStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
|
|
|
|
if (CFWriteStreamOpen(logger->logStream))
|
|
{
|
|
LOGGERDBG(CFSTR("-> stream open attempt, waiting for open completion"));
|
|
return YES;
|
|
}
|
|
|
|
LOGGERDBG(CFSTR("-> stream open failed."));
|
|
|
|
CFWriteStreamSetClient(logger->logStream, kCFStreamEventNone, NULL, NULL);
|
|
if (CFWriteStreamGetStatus(logger->logStream) == kCFStreamStatusOpen)
|
|
CFWriteStreamClose(logger->logStream);
|
|
CFWriteStreamUnscheduleFromRunLoop(logger->logStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
|
|
}
|
|
else
|
|
{
|
|
LOGGERDBG(CFSTR("-> stream set client failed."));
|
|
}
|
|
CFRelease(logger->logStream);
|
|
logger->logStream = NULL;
|
|
return NO;
|
|
}
|
|
|
|
static void LoggerTryConnect(Logger *logger)
|
|
{
|
|
// Core function that attempts connection to found Bonjour services and configured Host
|
|
LOGGERDBG(CFSTR("LoggerTryConnect, %d services registered, current stream=%@"), CFArrayGetCount(logger->bonjourServices), logger->logStream);
|
|
|
|
// If we already have a connection established or being attempted, stop here
|
|
if (logger->logStream != NULL)
|
|
{
|
|
LOGGERDBG(CFSTR("-> another connection is opened or in progress, giving up for now"));
|
|
return;
|
|
}
|
|
|
|
// If reachability status is not known yet, just wait
|
|
if (logger->targetReachable == NO)
|
|
{
|
|
LOGGERDBG(CFSTR("-> not sure target is reachable, let's wait and see"));
|
|
return;
|
|
}
|
|
|
|
// If there are discovered Bonjour services, try them now
|
|
while (CFArrayGetCount(logger->bonjourServices))
|
|
{
|
|
CFNetServiceRef service = (CFNetServiceRef)CFArrayGetValueAtIndex(logger->bonjourServices, 0);
|
|
LOGGERDBG(CFSTR("-> Trying to open write stream to service %@"), service);
|
|
CFStreamCreatePairWithSocketToNetService(NULL, service, NULL, &logger->logStream);
|
|
CFArrayRemoveValueAtIndex(logger->bonjourServices, 0);
|
|
if (logger->logStream == NULL)
|
|
{
|
|
// create pair failed
|
|
LOGGERDBG(CFSTR("-> failed."));
|
|
}
|
|
else if (LoggerConfigureAndOpenStream(logger))
|
|
{
|
|
// open is now in progress
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If there is a host to directly connect to, try it now (this will happen before
|
|
// Bonjour kicks in, Bonjour being handled as a fallback solution if direct Host
|
|
// fails)
|
|
if (logger->host != NULL)
|
|
{
|
|
LOGGERDBG(CFSTR("-> Trying to open direct connection to host %@ port %u"), logger->host, logger->port);
|
|
CFStreamCreatePairWithSocketToHost(NULL, logger->host, logger->port, NULL, &logger->logStream);
|
|
if (logger->logStream == NULL)
|
|
{
|
|
// Create stream failed
|
|
LOGGERDBG(CFSTR("-> failed."));
|
|
if (logger->logStream != NULL)
|
|
{
|
|
CFRelease(logger->logStream);
|
|
logger->logStream = NULL;
|
|
}
|
|
}
|
|
else if (LoggerConfigureAndOpenStream(logger))
|
|
{
|
|
// open is now in progress
|
|
return;
|
|
}
|
|
LoggerStartReconnectTimer(logger);
|
|
}
|
|
|
|
// Finally, if Bonjour is enabled and not started yet, start it now.
|
|
if (logger->options & kLoggerOption_BrowseBonjour)
|
|
{
|
|
if (logger->bonjourDomainBrowser == NULL || CFArrayGetCount(logger->bonjourServiceBrowsers) == 0)
|
|
{
|
|
LoggerStopBonjourBrowsing(logger);
|
|
LoggerStartBonjourBrowsing(logger);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void LoggerWriteStreamTerminated(Logger *logger)
|
|
{
|
|
LOGGERDBG(CFSTR("LoggerWriteStreamTerminated called"));
|
|
|
|
if (logger->connected)
|
|
{
|
|
LOGGERDBG(CFSTR("-> Logger DISCONNECTED"));
|
|
logger->connected = NO;
|
|
}
|
|
|
|
if (logger->logStream != NULL)
|
|
{
|
|
LOGGERDBG(CFSTR("-> disposing the write stream"));
|
|
CFWriteStreamSetClient(logger->logStream, 0, NULL, NULL);
|
|
CFWriteStreamUnscheduleFromRunLoop(logger->logStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
|
CFWriteStreamClose(logger->logStream);
|
|
|
|
CFRelease(logger->logStream);
|
|
logger->logStream = NULL;
|
|
}
|
|
|
|
if (logger->bufferReadStream != NULL)
|
|
{
|
|
// In the case the connection drops before we have flushed the
|
|
// whole contents of the file, we choose to keep it integrally
|
|
// and retransmit it when reconnecting to the viewer. The reason
|
|
// of this choice is that we may have transmitted only part of
|
|
// a message, and this may cause errors on the desktop side.
|
|
LOGGERDBG(CFSTR("-> closing the bufferReadStream"));
|
|
CFReadStreamClose(logger->bufferReadStream);
|
|
CFRelease(logger->bufferReadStream);
|
|
logger->bufferReadStream = NULL;
|
|
}
|
|
|
|
if (logger->bufferFile != NULL && logger->bufferWriteStream == NULL)
|
|
LoggerCreateBufferWriteStream(logger);
|
|
|
|
// ensure that any current block on LoggerFlush() gets unblocked
|
|
pthread_cond_broadcast(&logger->logQueueEmpty);
|
|
|
|
// tryConnect will take care of setting up the reconnect timer if needed
|
|
if (logger->targetReachable &&
|
|
(logger->host != NULL || (logger->options & kLoggerOption_BrowseBonjour)))
|
|
LoggerTryConnect(logger);
|
|
}
|
|
|
|
static void LoggerWriteStreamCallback(CFWriteStreamRef ws, CFStreamEventType event, void* info)
|
|
{
|
|
Logger *logger = (Logger *)info;
|
|
assert(ws == logger->logStream);
|
|
switch (event)
|
|
{
|
|
case kCFStreamEventOpenCompleted:
|
|
// A stream open was complete. Cancel all bonjour browsing,
|
|
// service resolution and connection attempts, and try to
|
|
// write existing buffer contents
|
|
LOGGERDBG(CFSTR("Logger CONNECTED"));
|
|
logger->connected = YES;
|
|
LoggerStopBonjourBrowsing(logger);
|
|
LoggerStopReconnectTimer(logger);
|
|
if (logger->bufferWriteStream != NULL)
|
|
{
|
|
// now that a connection is acquired, we can stop logging to a file
|
|
CFWriteStreamClose(logger->bufferWriteStream);
|
|
CFRelease(logger->bufferWriteStream);
|
|
logger->bufferWriteStream = NULL;
|
|
}
|
|
if (logger->bufferFile != NULL)
|
|
{
|
|
// if a buffer file was defined, try to read its contents
|
|
LoggerCreateBufferReadStream(logger);
|
|
}
|
|
LoggerPushClientInfoToFrontOfQueue(logger);
|
|
LoggerWriteMoreData(logger);
|
|
break;
|
|
|
|
case kCFStreamEventCanAcceptBytes:
|
|
LoggerWriteMoreData(logger);
|
|
break;
|
|
|
|
case kCFStreamEventErrorOccurred: {
|
|
CFErrorRef error = CFWriteStreamCopyError(ws);
|
|
LOGGERDBG(CFSTR("Logger stream error: %@"), error);
|
|
CFRelease(error);
|
|
// Fall-thru
|
|
}
|
|
|
|
case kCFStreamEventEndEncountered:
|
|
LoggerWriteStreamTerminated(logger);
|
|
break;
|
|
|
|
// avoid warnings when building; cover all enum cases.
|
|
case kCFStreamEventNone:
|
|
case kCFStreamEventHasBytesAvailable:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark Internal encoding functions
|
|
// -----------------------------------------------------------------------------
|
|
static uint8_t *LoggerMessagePrepareForPart(CFMutableDataRef encoder, uint32_t requiredExtraBytes)
|
|
{
|
|
// Ensure a data block has the required storage capacity, update the total size and part count
|
|
// then return a pointer for fast storage of the data
|
|
uint8_t *p = CFDataGetMutableBytePtr(encoder);
|
|
CFIndex size = CFDataGetLength(encoder);
|
|
uint32_t oldSize = ntohl(*(uint32_t *)p);
|
|
uint32_t newSize = oldSize + requiredExtraBytes;
|
|
if ((newSize + 4) > size)
|
|
{
|
|
// grow by 64 bytes chunks
|
|
CFDataSetLength(encoder, (newSize + 4 + 64) & ~63);
|
|
p = CFDataGetMutableBytePtr(encoder);
|
|
}
|
|
*((uint32_t *)p) = htonl(newSize);
|
|
p += 4;
|
|
*((uint16_t *)p) = htons(ntohs(*(uint16_t *)p) + 1);
|
|
|
|
// return a pointer to where new data must be put
|
|
return p + oldSize;
|
|
}
|
|
|
|
static void LoggerMessageAddTimestamp(CFMutableDataRef encoder)
|
|
{
|
|
struct timeval t;
|
|
if (gettimeofday(&t, NULL) == 0)
|
|
{
|
|
#if __LP64__
|
|
LoggerMessageAddInt64(encoder, t.tv_sec, PART_KEY_TIMESTAMP_S);
|
|
LoggerMessageAddInt64(encoder, t.tv_usec, PART_KEY_TIMESTAMP_US);
|
|
#else
|
|
LoggerMessageAddInt32(encoder, t.tv_sec, PART_KEY_TIMESTAMP_S);
|
|
LoggerMessageAddInt32(encoder, t.tv_usec, PART_KEY_TIMESTAMP_US);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
time_t ts = time(NULL);
|
|
#if __LP64__
|
|
LoggerMessageAddInt64(encoder, ts, PART_KEY_TIMESTAMP_S);
|
|
#else
|
|
LoggerMessageAddInt32(encoder, ts, PART_KEY_TIMESTAMP_S);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void LoggerMessageAddTimestampAndThreadID(CFMutableDataRef encoder)
|
|
{
|
|
LoggerMessageAddTimestamp(encoder);
|
|
|
|
BOOL hasThreadName = NO;
|
|
#if ALLOW_COCOA_USE
|
|
// Getting the thread number is tedious, to say the least. Since there is
|
|
// no direct way to get it, we have to do it sideways. Note that it can be dangerous
|
|
// to use any Cocoa call when in a multithreaded application that only uses non-Cocoa threads
|
|
// and for which Cocoa's multithreading has not been activated. We test for this case.
|
|
BOOL inMainThread = [NSThread isMainThread];
|
|
if (inMainThread)
|
|
{
|
|
hasThreadName = YES;
|
|
LoggerMessageAddString(encoder, CFSTR("Main thread"), PART_KEY_THREAD_ID);
|
|
}
|
|
else if ([NSThread isMultiThreaded])
|
|
{
|
|
NSThread *thread = [NSThread currentThread];
|
|
NSString *name = [thread name];
|
|
if (![name length])
|
|
{
|
|
// use the thread dictionary to store and retrieve the computed thread name
|
|
NSMutableDictionary *threadDict = [thread threadDictionary];
|
|
name = [threadDict objectForKey:@"__$NSLoggerThreadName$__"];
|
|
if (name == nil)
|
|
{
|
|
@autoreleasepool {
|
|
// optimize CPU use by computing the thread name once and storing it back
|
|
// in the thread dictionary
|
|
name = [thread description];
|
|
NSRange range = [name rangeOfString:@"num = "];
|
|
if (range.location != NSNotFound)
|
|
{
|
|
name = [NSString stringWithFormat:@"Thread %@",
|
|
[name substringWithRange:NSMakeRange(range.location + range.length,
|
|
[name length] - range.location - range.length - 1)]];
|
|
[threadDict setObject:name forKey:@"__$NSLoggerThreadName$__"];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (name != nil)
|
|
{
|
|
LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)name, PART_KEY_THREAD_ID);
|
|
hasThreadName = YES;
|
|
}
|
|
}
|
|
#endif
|
|
if (!hasThreadName)
|
|
{
|
|
#if __LP64__
|
|
LoggerMessageAddInt64(encoder, (int64_t)pthread_self(), PART_KEY_THREAD_ID);
|
|
#else
|
|
LoggerMessageAddInt32(encoder, (int32_t)pthread_self(), PART_KEY_THREAD_ID);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static CFMutableDataRef LoggerMessageCreate(int32_t seq)
|
|
{
|
|
CFMutableDataRef encoder = CFDataCreateMutable(NULL, 0);
|
|
if (encoder != NULL)
|
|
{
|
|
CFDataIncreaseLength(encoder, 64);
|
|
uint8_t *p = CFDataGetMutableBytePtr(encoder);
|
|
if (p != NULL)
|
|
{
|
|
// directly write the sequence number as first part of the message
|
|
// so we find it quickly when inserting new messages in the queue
|
|
if (seq)
|
|
{
|
|
p[3] = 8; // size 0x00000008 in big endian
|
|
p[5] = 1; // part count 0x0001
|
|
p[6] = (uint8_t)PART_KEY_MESSAGE_SEQ;
|
|
p[7] = (uint8_t)PART_TYPE_INT32;
|
|
*(uint32_t *)(p + 8) = htonl(seq); // ARMv6 and later, x86 processors do just fine with unaligned accesses
|
|
}
|
|
else
|
|
{
|
|
// empty message with a 0 part count
|
|
p[3] = 2;
|
|
}
|
|
}
|
|
LoggerMessageAddTimestampAndThreadID(encoder);
|
|
}
|
|
return encoder;
|
|
}
|
|
|
|
static void LoggerMessageFinalize(CFMutableDataRef encoder)
|
|
{
|
|
// Finalize a message by reducing the CFData size to the actual used size
|
|
if (encoder != NULL)
|
|
{
|
|
uint32_t *p = (uint32_t *)CFDataGetBytePtr(encoder);
|
|
if (p != NULL)
|
|
CFDataSetLength(encoder, ntohl(*p) + 4);
|
|
}
|
|
}
|
|
|
|
static void LoggerMessageAddInt32(CFMutableDataRef encoder, int32_t anInt, int key)
|
|
{
|
|
uint8_t *p = LoggerMessagePrepareForPart(encoder, 6);
|
|
if (p != NULL)
|
|
{
|
|
*p++ = (uint8_t)key;
|
|
*p++ = (uint8_t)PART_TYPE_INT32;
|
|
*(uint32_t *)p = htonl(anInt); // ARMv6 and later, x86 processors do just fine with unaligned accesses
|
|
}
|
|
}
|
|
|
|
#if __LP64__
|
|
static void LoggerMessageAddInt64(CFMutableDataRef encoder, int64_t anInt, int key)
|
|
{
|
|
uint8_t *p = LoggerMessagePrepareForPart(encoder, 10);
|
|
if (p != NULL)
|
|
{
|
|
*p++ = (uint8_t)key;
|
|
*p++ = (uint8_t)PART_TYPE_INT64;
|
|
uint32_t *q = (uint32_t *)p;
|
|
*q++ = htonl((uint32_t)(anInt >> 32)); // ARMv6 and later, x86 processors do just fine with unaligned accesses
|
|
*q = htonl((uint32_t)anInt);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void LoggerMessageAddCString(CFMutableDataRef data, const char *aString, int key)
|
|
{
|
|
if (aString == NULL || *aString == 0)
|
|
return;
|
|
|
|
// convert to UTF-8
|
|
int len = (int)strlen(aString);
|
|
uint8_t *buf = malloc((size_t)(2 * len));
|
|
if (buf != NULL)
|
|
{
|
|
int i, n = 0;
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
uint8_t c = (uint8_t)(*aString++);
|
|
if (c < 0x80)
|
|
buf[n++] = c;
|
|
else {
|
|
buf[n++] = 0xC0 | (c >> 6);
|
|
buf[n++] = (c & 0x6F) | 0x80;
|
|
}
|
|
}
|
|
if (n)
|
|
{
|
|
uint8_t *p = LoggerMessagePrepareForPart(data, (uint32_t)n+6);
|
|
if (p != NULL)
|
|
{
|
|
*p++ = (uint8_t)key;
|
|
*p++ = (uint8_t)PART_TYPE_STRING;
|
|
*(uint32_t *)p = htonl(n); // ARMv6 and later, x86 processors do just fine with unaligned accesses
|
|
memcpy(p + 4, buf, (size_t)n);
|
|
}
|
|
}
|
|
free(buf);
|
|
}
|
|
}
|
|
|
|
static void LoggerMessageAddString(CFMutableDataRef encoder, CFStringRef aString, int key)
|
|
{
|
|
if (aString == NULL)
|
|
aString = CFSTR("");
|
|
|
|
// All strings are UTF-8 encoded
|
|
uint32_t partSize = 0;
|
|
uint8_t *bytes = NULL;
|
|
|
|
CFIndex stringLength = CFStringGetLength(aString);
|
|
CFIndex bytesLength = stringLength * 4;
|
|
if (stringLength)
|
|
{
|
|
bytes = (uint8_t *)malloc((size_t)bytesLength + 4);
|
|
if (bytes != NULL)
|
|
{
|
|
CFStringGetBytes(aString, CFRangeMake(0, stringLength), kCFStringEncodingUTF8, '?', false, bytes, bytesLength, &bytesLength);
|
|
partSize = (uint32_t)bytesLength;
|
|
}
|
|
}
|
|
|
|
uint8_t *p = LoggerMessagePrepareForPart(encoder, 6 + partSize);
|
|
if (p != NULL)
|
|
{
|
|
*p++ = (uint8_t)key;
|
|
*p++ = (uint8_t)PART_TYPE_STRING;
|
|
*(uint32_t *)p = htonl(partSize); // ARMv6 and later, x86 processors do just fine with unaligned accesses
|
|
if (partSize && bytes != NULL)
|
|
memcpy(p + 4, bytes, (size_t)partSize);
|
|
}
|
|
|
|
if (bytes != NULL)
|
|
free(bytes);
|
|
}
|
|
|
|
static void LoggerMessageAddData(CFMutableDataRef encoder, CFDataRef theData, int key, int partType)
|
|
{
|
|
if (theData != NULL)
|
|
{
|
|
CFIndex dataLength = CFDataGetLength(theData);
|
|
uint8_t *p = LoggerMessagePrepareForPart(encoder, (uint32_t)dataLength + 6);
|
|
if (p != NULL)
|
|
{
|
|
*p++ = (uint8_t)key;
|
|
*p++ = (uint8_t)partType;
|
|
*((uint32_t *)p) = htonl(dataLength); // ARMv6 and later, x86 processors do just fine with unaligned accesses
|
|
if (dataLength)
|
|
memcpy(p + 4, CFDataGetBytePtr(theData), (size_t)dataLength);
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint32_t LoggerMessageGetSeq(CFDataRef message)
|
|
{
|
|
// Extract the sequence number from a message. When pushing messages to the queue,
|
|
// we use this to guarantee the logging order according to the seq#
|
|
// Since we now store the seq as first component, we only have to check whether
|
|
// the first part is the sequence number, and extract it.
|
|
uint8_t *p = (uint8_t *)CFDataGetBytePtr(message) + 4;
|
|
uint16_t partCount = ntohs(*(uint16_t *)p);
|
|
if (partCount)
|
|
{
|
|
if (p[2] == PART_KEY_MESSAGE_SEQ)
|
|
return ntohl(*(uint32_t *)(p+4)); // ARMv6 and later, x86 processors do just fine with unaligned accesses
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark Private logging functions
|
|
// -----------------------------------------------------------------------------
|
|
static void LoggerPushClientInfoToFrontOfQueue(Logger *logger)
|
|
{
|
|
// Extract client information from the main bundle, as well as platform info,
|
|
// and assemble it to a message that will be put in front of the queue
|
|
// Helps desktop viewer display who's talking to it
|
|
// Note that we must be called from the logger work thread, as we don't
|
|
// run through the message port to transmit this message to the queue
|
|
CFBundleRef bundle = CFBundleGetMainBundle();
|
|
if (bundle == NULL)
|
|
return;
|
|
CFMutableDataRef encoder = LoggerMessageCreate(0);
|
|
if (encoder != NULL)
|
|
{
|
|
LoggerMessageAddInt32(encoder, LOGMSG_TYPE_CLIENTINFO, PART_KEY_MESSAGE_TYPE);
|
|
|
|
CFStringRef version = (CFStringRef)CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleVersionKey);
|
|
if (version != NULL && CFGetTypeID(version) == CFStringGetTypeID())
|
|
LoggerMessageAddString(encoder, version, PART_KEY_CLIENT_VERSION);
|
|
CFStringRef name = (CFStringRef)CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleNameKey);
|
|
if (name != NULL)
|
|
LoggerMessageAddString(encoder, name, PART_KEY_CLIENT_NAME);
|
|
|
|
#if TARGET_OS_IPHONE && ALLOW_COCOA_USE
|
|
if ([NSThread isMultiThreaded] || [NSThread isMainThread])
|
|
{
|
|
@autoreleasepool
|
|
{
|
|
UIDevice *device = [UIDevice currentDevice];
|
|
LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)device.name, PART_KEY_UNIQUEID);
|
|
LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)device.systemVersion, PART_KEY_OS_VERSION);
|
|
LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)device.systemName, PART_KEY_OS_NAME);
|
|
LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)device.model, PART_KEY_CLIENT_MODEL);
|
|
}
|
|
}
|
|
#elif TARGET_OS_MAC
|
|
CFStringRef osName = NULL, osVersion = NULL;
|
|
#if ALLOW_COCOA_USE
|
|
// Read the OS version without using deprecated Gestalt calls
|
|
@autoreleasepool
|
|
{
|
|
@try
|
|
{
|
|
NSString* versionString = [[NSDictionary dictionaryWithContentsOfFile: @"/System/Library/CoreServices/SystemVersion.plist"] objectForKey: @"ProductVersion"];
|
|
if ([versionString length])
|
|
{
|
|
osName = CFSTR("Mac OS X");
|
|
osVersion = CFRetain((CAST_TO_CFSTRING)versionString);
|
|
}
|
|
}
|
|
@catch (NSException *exc)
|
|
{
|
|
}
|
|
}
|
|
#endif
|
|
if (osVersion == NULL)
|
|
{
|
|
// Not allowed to call into Cocoa ? use the Darwin version string
|
|
struct utsname u;
|
|
if (uname(&u) == 0)
|
|
{
|
|
osName = CFStringCreateWithCString(NULL, u.sysname, kCFStringEncodingUTF8);
|
|
osVersion = CFStringCreateWithCString(NULL, u.release, kCFStringEncodingUTF8);
|
|
}
|
|
else
|
|
{
|
|
osName = CFSTR("Mac OS X");
|
|
osVersion = CFSTR("");
|
|
}
|
|
}
|
|
LoggerMessageAddString(encoder, osVersion, PART_KEY_OS_VERSION);
|
|
LoggerMessageAddString(encoder, osName, PART_KEY_OS_NAME);
|
|
CFRelease(osVersion);
|
|
CFRelease(osName);
|
|
|
|
char buf[64];
|
|
size_t len;
|
|
int ncpu = 0;
|
|
bzero(buf, sizeof(buf));
|
|
len = sizeof(buf)-1;
|
|
sysctlbyname("hw.model", buf, &len, NULL, 0);
|
|
len = sizeof(ncpu);
|
|
sysctlbyname("hw.ncpu", &ncpu, &len, NULL, 0);
|
|
sprintf(buf+strlen(buf), " - %d * ", ncpu);
|
|
len = sizeof(buf)-strlen(buf)-1;
|
|
sysctlbyname("hw.machine", buf+strlen(buf), &len, NULL, 0);
|
|
|
|
CFStringRef s = CFStringCreateWithCString(NULL, buf, kCFStringEncodingASCII);
|
|
LoggerMessageAddString(encoder, s, PART_KEY_CLIENT_MODEL);
|
|
CFRelease(s);
|
|
#endif
|
|
LoggerMessageFinalize(encoder);
|
|
|
|
pthread_mutex_lock(&logger->logQueueMutex);
|
|
CFArrayInsertValueAtIndex(logger->logQueue, logger->incompleteSendOfFirstItem ? 1 : 0, encoder);
|
|
pthread_mutex_unlock(&logger->logQueueMutex);
|
|
|
|
CFRelease(encoder);
|
|
}
|
|
}
|
|
|
|
static void LoggerPushMessageToQueue(Logger *logger, CFDataRef message)
|
|
{
|
|
// Add the message to the log queue and signal the runLoop source that will trigger
|
|
// a send on the worker thread.
|
|
pthread_mutex_lock(&logger->logQueueMutex);
|
|
CFIndex idx = CFArrayGetCount(logger->logQueue);
|
|
if (idx)
|
|
{
|
|
// to prevent out-of-order messages (as much as possible), we try to transmit messages in the
|
|
// order their sequence number was generated. Since the seq is generated first-thing,
|
|
// we can provide fine-grained ordering that gives a reasonable idea of the order
|
|
// the logging calls were made (useful for precise information about multithreading code)
|
|
uint32_t lastSeq, seq = LoggerMessageGetSeq(message);
|
|
do {
|
|
lastSeq = LoggerMessageGetSeq(CFArrayGetValueAtIndex(logger->logQueue, idx-1));
|
|
} while (lastSeq > seq && --idx > 0);
|
|
}
|
|
if (idx >= 0)
|
|
CFArrayInsertValueAtIndex(logger->logQueue, idx, message);
|
|
else
|
|
CFArrayAppendValue(logger->logQueue, message);
|
|
pthread_mutex_unlock(&logger->logQueueMutex);
|
|
|
|
if (logger->messagePushedSource != NULL)
|
|
{
|
|
// One case where the pushed source may be NULL is if the client code
|
|
// immediately starts logging without initializing the logger first.
|
|
// In this case, the worker thread has not completed startup, so we don't need
|
|
// to fire the runLoop source
|
|
CFRunLoopSourceSignal(logger->messagePushedSource);
|
|
}
|
|
else if (logger->workerThread == NULL && (logger->options & kLoggerOption_LogToConsole))
|
|
{
|
|
// In this case, a failure creating the message runLoop source forces us
|
|
// to always log to console
|
|
pthread_mutex_lock(&logger->logQueueMutex);
|
|
while (CFArrayGetCount(logger->logQueue))
|
|
{
|
|
LoggerLogToConsole(CFArrayGetValueAtIndex(logger->logQueue, 0));
|
|
CFArrayRemoveValueAtIndex(logger->logQueue, 0);
|
|
}
|
|
pthread_mutex_unlock(&logger->logQueueMutex);
|
|
pthread_cond_broadcast(&logger->logQueueEmpty); // in case other threads are waiting for a flush
|
|
}
|
|
}
|
|
|
|
static void LogMessageRawTo_internal(Logger *logger,
|
|
const char *filename,
|
|
int lineNumber,
|
|
const char *functionName,
|
|
NSString *domain,
|
|
int level,
|
|
NSString *message)
|
|
{
|
|
// Variant of the LogMessage function that doesn't perform any variable arguments formatting
|
|
logger = LoggerStart(logger); // start if needed
|
|
if (logger != NULL)
|
|
{
|
|
int32_t seq = OSAtomicIncrement32Barrier(&logger->messageSeq);
|
|
LOGGERDBG2(CFSTR("%ld LogMessage"), seq);
|
|
|
|
CFMutableDataRef encoder = LoggerMessageCreate(seq);
|
|
if (encoder != NULL)
|
|
{
|
|
LoggerMessageAddInt32(encoder, LOGMSG_TYPE_LOG, PART_KEY_MESSAGE_TYPE);
|
|
if (domain != nil && [domain length])
|
|
LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)domain, PART_KEY_TAG);
|
|
if (level)
|
|
LoggerMessageAddInt32(encoder, level, PART_KEY_LEVEL);
|
|
if (filename != NULL)
|
|
LoggerMessageAddCString(encoder, filename, PART_KEY_FILENAME);
|
|
if (lineNumber)
|
|
LoggerMessageAddInt32(encoder, lineNumber, PART_KEY_LINENUMBER);
|
|
if (functionName != NULL)
|
|
LoggerMessageAddCString(encoder, functionName, PART_KEY_FUNCTIONNAME);
|
|
if (message != nil)
|
|
LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)message, PART_KEY_MESSAGE);
|
|
else
|
|
LoggerMessageAddString(encoder, CFSTR(""), PART_KEY_MESSAGE);
|
|
|
|
LoggerMessageFinalize(encoder);
|
|
LoggerPushMessageToQueue(logger, encoder);
|
|
CFRelease(encoder);
|
|
}
|
|
else
|
|
{
|
|
LOGGERDBG2(CFSTR("-> failed creating encoder"));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void LogMessageTo_internal(Logger *logger,
|
|
const char *filename,
|
|
int lineNumber,
|
|
const char *functionName,
|
|
NSString *domain,
|
|
int level,
|
|
NSString *format,
|
|
va_list args)
|
|
{
|
|
logger = LoggerStart(logger); // start if needed
|
|
if (logger != NULL)
|
|
{
|
|
int32_t seq = OSAtomicIncrement32Barrier(&logger->messageSeq);
|
|
LOGGERDBG2(CFSTR("%ld LogMessage"), seq);
|
|
|
|
CFMutableDataRef encoder = LoggerMessageCreate(seq);
|
|
if (encoder != NULL)
|
|
{
|
|
LoggerMessageAddInt32(encoder, LOGMSG_TYPE_LOG, PART_KEY_MESSAGE_TYPE);
|
|
if (domain != nil && [domain length])
|
|
LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)domain, PART_KEY_TAG);
|
|
if (level)
|
|
LoggerMessageAddInt32(encoder, level, PART_KEY_LEVEL);
|
|
if (filename != NULL)
|
|
LoggerMessageAddCString(encoder, filename, PART_KEY_FILENAME);
|
|
if (lineNumber)
|
|
LoggerMessageAddInt32(encoder, lineNumber, PART_KEY_LINENUMBER);
|
|
if (functionName != NULL)
|
|
LoggerMessageAddCString(encoder, functionName, PART_KEY_FUNCTIONNAME);
|
|
|
|
#if ALLOW_COCOA_USE
|
|
// Go though NSString to avoid low-level logging of CF datastructures (i.e. too detailed NSDictionary, etc)
|
|
NSString *msgString = [[NSString alloc] initWithFormat:format arguments:args];
|
|
if (msgString != nil)
|
|
{
|
|
LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)msgString, PART_KEY_MESSAGE);
|
|
RELEASE(msgString);
|
|
}
|
|
#else
|
|
CFStringRef msgString = CFStringCreateWithFormatAndArguments(NULL, NULL, (CFStringRef)format, args);
|
|
if (msgString != NULL)
|
|
{
|
|
LoggerMessageAddString(encoder, msgString, PART_KEY_MESSAGE);
|
|
CFRelease(msgString);
|
|
}
|
|
#endif
|
|
|
|
LoggerMessageFinalize(encoder);
|
|
LoggerPushMessageToQueue(logger, encoder);
|
|
CFRelease(encoder);
|
|
}
|
|
else
|
|
{
|
|
LOGGERDBG2(CFSTR("-> failed creating encoder"));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void LogImageTo_internal(Logger *logger,
|
|
const char *filename,
|
|
int lineNumber,
|
|
const char *functionName,
|
|
NSString *domain,
|
|
int level,
|
|
int width,
|
|
int height,
|
|
NSData *data)
|
|
{
|
|
logger = LoggerStart(logger); // start if needed
|
|
if (logger != NULL)
|
|
{
|
|
int32_t seq = OSAtomicIncrement32Barrier(&logger->messageSeq);
|
|
LOGGERDBG2(CFSTR("%ld LogImage"), seq);
|
|
|
|
CFMutableDataRef encoder = LoggerMessageCreate(seq);
|
|
if (encoder != NULL)
|
|
{
|
|
LoggerMessageAddInt32(encoder, LOGMSG_TYPE_LOG, PART_KEY_MESSAGE_TYPE);
|
|
if (domain != nil && [domain length])
|
|
LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)domain, PART_KEY_TAG);
|
|
if (level)
|
|
LoggerMessageAddInt32(encoder, level, PART_KEY_LEVEL);
|
|
if (width && height)
|
|
{
|
|
LoggerMessageAddInt32(encoder, width, PART_KEY_IMAGE_WIDTH);
|
|
LoggerMessageAddInt32(encoder, height, PART_KEY_IMAGE_HEIGHT);
|
|
}
|
|
if (filename != NULL)
|
|
LoggerMessageAddCString(encoder, filename, PART_KEY_FILENAME);
|
|
if (lineNumber)
|
|
LoggerMessageAddInt32(encoder, lineNumber, PART_KEY_LINENUMBER);
|
|
if (functionName != NULL)
|
|
LoggerMessageAddCString(encoder, functionName, PART_KEY_FUNCTIONNAME);
|
|
LoggerMessageAddData(encoder, (CAST_TO_CFDATA)data, PART_KEY_MESSAGE, PART_TYPE_IMAGE);
|
|
|
|
LoggerMessageFinalize(encoder);
|
|
LoggerPushMessageToQueue(logger, encoder);
|
|
CFRelease(encoder);
|
|
}
|
|
else
|
|
{
|
|
LOGGERDBG2(CFSTR("-> failed creating encoder"));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void LogDataTo_internal(Logger *logger,
|
|
const char *filename,
|
|
int lineNumber,
|
|
const char *functionName,
|
|
NSString *domain,
|
|
int level, NSData *data)
|
|
{
|
|
logger = LoggerStart(logger); // start if needed
|
|
if (logger != NULL)
|
|
{
|
|
int32_t seq = OSAtomicIncrement32Barrier(&logger->messageSeq);
|
|
LOGGERDBG2(CFSTR("%ld LogData"), seq);
|
|
|
|
CFMutableDataRef encoder = LoggerMessageCreate(seq);
|
|
if (encoder != NULL)
|
|
{
|
|
LoggerMessageAddInt32(encoder, LOGMSG_TYPE_LOG, PART_KEY_MESSAGE_TYPE);
|
|
if (domain != nil && [domain length])
|
|
LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)domain, PART_KEY_TAG);
|
|
if (level)
|
|
LoggerMessageAddInt32(encoder, level, PART_KEY_LEVEL);
|
|
if (filename != NULL)
|
|
LoggerMessageAddCString(encoder, filename, PART_KEY_FILENAME);
|
|
if (lineNumber)
|
|
LoggerMessageAddInt32(encoder, lineNumber, PART_KEY_LINENUMBER);
|
|
if (functionName != NULL)
|
|
LoggerMessageAddCString(encoder, functionName, PART_KEY_FUNCTIONNAME);
|
|
LoggerMessageAddData(encoder, (CAST_TO_CFDATA)data, PART_KEY_MESSAGE, PART_TYPE_BINARY);
|
|
|
|
LoggerMessageFinalize(encoder);
|
|
LoggerPushMessageToQueue(logger, encoder);
|
|
CFRelease(encoder);
|
|
}
|
|
else
|
|
{
|
|
LOGGERDBG2(CFSTR("-> failed creating encoder"));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void LogStartBlockTo_internal(Logger *logger, NSString *format, va_list args)
|
|
{
|
|
logger = LoggerStart(logger); // start if needed
|
|
if (logger)
|
|
{
|
|
int32_t seq = OSAtomicIncrement32Barrier(&logger->messageSeq);
|
|
LOGGERDBG2(CFSTR("%ld LogStartBlock"), seq);
|
|
|
|
CFMutableDataRef encoder = LoggerMessageCreate(seq);
|
|
if (encoder != NULL)
|
|
{
|
|
LoggerMessageAddInt32(encoder, LOGMSG_TYPE_BLOCKSTART, PART_KEY_MESSAGE_TYPE);
|
|
if (format != nil)
|
|
{
|
|
CFStringRef msgString = CFStringCreateWithFormatAndArguments(NULL, NULL, (CAST_TO_CFSTRING)format, args);
|
|
if (msgString != NULL)
|
|
{
|
|
LoggerMessageAddString(encoder, msgString, PART_KEY_MESSAGE);
|
|
CFRelease(msgString);
|
|
}
|
|
}
|
|
|
|
LoggerMessageFinalize(encoder);
|
|
LoggerPushMessageToQueue(logger, encoder);
|
|
CFRelease(encoder);
|
|
}
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark Public logging functions
|
|
// -----------------------------------------------------------------------------
|
|
void LogMessageRaw(NSString *message)
|
|
{
|
|
LogMessageRawTo_internal(NULL, NULL, 0, NULL, nil, 0, message);
|
|
}
|
|
|
|
void LogMessageRawF(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *message)
|
|
{
|
|
LogMessageRawTo_internal(NULL, filename, lineNumber, functionName, domain, level, message);
|
|
}
|
|
|
|
void LogMessageRawToF(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *message)
|
|
{
|
|
LogMessageRawTo_internal(logger, filename, lineNumber, functionName, domain, level, message);
|
|
}
|
|
|
|
void LogMessageCompat(NSString *format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
LogMessageTo_internal(NULL, NULL, 0, NULL, nil, 0, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void LogMessageTo(Logger *logger, NSString *domain, int level, NSString *format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
LogMessageTo_internal(logger, NULL, 0, NULL, domain, level, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void LogMessageToF(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
LogMessageTo_internal(logger, filename, lineNumber, functionName, domain, level, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void LogMessageTo_va(Logger *logger, NSString *domain, int level, NSString *format, va_list args)
|
|
{
|
|
LogMessageTo_internal(logger, NULL, 0, NULL, domain, level, format, args);
|
|
}
|
|
|
|
void LogMessageToF_va(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, va_list args)
|
|
{
|
|
LogMessageTo_internal(logger, filename, lineNumber, functionName, domain, level, format, args);
|
|
}
|
|
|
|
void LogMessage(NSString *domain, int level, NSString *format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
LogMessageTo_internal(NULL, NULL, 0, NULL, domain, level, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void LogMessageF(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
LogMessageTo_internal(NULL, filename, lineNumber, functionName, domain, level, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void LogMessage_va(NSString *domain, int level, NSString *format, va_list args)
|
|
{
|
|
LogMessageTo_internal(NULL, NULL, 0, NULL, domain, level, format, args);
|
|
}
|
|
|
|
void LogMessageF_va(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, va_list args)
|
|
{
|
|
LogMessageTo_internal(NULL, filename, lineNumber, functionName, domain, level, format, args);
|
|
}
|
|
|
|
void LogData(NSString *domain, int level, NSData *data)
|
|
{
|
|
LogDataTo_internal(NULL, NULL, 0, NULL, domain, level, data);
|
|
}
|
|
|
|
void LogDataF(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSData *data)
|
|
{
|
|
LogDataTo_internal(NULL, filename, lineNumber, functionName, domain, level, data);
|
|
}
|
|
|
|
void LogDataTo(Logger *logger, NSString *domain, int level, NSData *data)
|
|
{
|
|
LogDataTo_internal(logger, NULL, 0, NULL, domain, level, data);
|
|
}
|
|
|
|
void LogDataToF(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSData *data)
|
|
{
|
|
LogDataTo_internal(logger, filename, lineNumber, functionName, domain, level, data);
|
|
}
|
|
|
|
void LogImageData(NSString *domain, int level, int width, int height, NSData *data)
|
|
{
|
|
LogImageTo_internal(NULL, NULL, 0, NULL, domain, level, width, height, data);
|
|
}
|
|
|
|
void LogImageDataF(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, int width, int height, NSData *data)
|
|
{
|
|
LogImageTo_internal(NULL, filename, lineNumber, functionName, domain, level, width, height, data);
|
|
}
|
|
|
|
void LogImageDataTo(Logger *logger, NSString *domain, int level, int width, int height, NSData *data)
|
|
{
|
|
LogImageTo_internal(logger, NULL, 0, NULL, domain, level, width, height, data);
|
|
}
|
|
|
|
void LogImageDataToF(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, int width, int height, NSData *data)
|
|
{
|
|
LogImageTo_internal(logger, filename, lineNumber, functionName, domain, level, width, height, data);
|
|
}
|
|
|
|
void LogStartBlock(NSString *format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
LogStartBlockTo_internal(NULL, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void LogStartBlockTo(Logger *logger, NSString *format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
LogStartBlockTo_internal(logger, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void LogEndBlockTo(Logger *logger)
|
|
{
|
|
logger = LoggerStart(logger);
|
|
if (logger)
|
|
{
|
|
if (logger->options & kLoggerOption_LogToConsole)
|
|
return;
|
|
|
|
int32_t seq = OSAtomicIncrement32Barrier(&logger->messageSeq);
|
|
LOGGERDBG2(CFSTR("%ld LogEndBlock"), seq);
|
|
|
|
CFMutableDataRef encoder = LoggerMessageCreate(seq);
|
|
if (encoder != NULL)
|
|
{
|
|
LoggerMessageAddInt32(encoder, LOGMSG_TYPE_BLOCKEND, PART_KEY_MESSAGE_TYPE);
|
|
LoggerMessageFinalize(encoder);
|
|
LoggerPushMessageToQueue(logger, encoder);
|
|
CFRelease(encoder);
|
|
}
|
|
else
|
|
{
|
|
LOGGERDBG2(CFSTR("-> failed creating encoder"));
|
|
}
|
|
}
|
|
}
|
|
|
|
void LogEndBlock(void)
|
|
{
|
|
LogEndBlockTo(NULL);
|
|
}
|
|
|
|
void LogMarkerTo(Logger *logger, NSString *text)
|
|
{
|
|
logger = LoggerStart(logger); // start if needed
|
|
if (logger != NULL)
|
|
{
|
|
int32_t seq = OSAtomicIncrement32Barrier(&logger->messageSeq);
|
|
LOGGERDBG2(CFSTR("%ld LogMarker"), seq);
|
|
|
|
CFMutableDataRef encoder = LoggerMessageCreate(seq);
|
|
if (encoder != NULL)
|
|
{
|
|
LoggerMessageAddInt32(encoder, LOGMSG_TYPE_MARK, PART_KEY_MESSAGE_TYPE);
|
|
if (text == nil)
|
|
{
|
|
CFDateFormatterRef df = CFDateFormatterCreate(NULL, NULL, kCFDateFormatterShortStyle, kCFDateFormatterMediumStyle);
|
|
CFStringRef str = CFDateFormatterCreateStringWithAbsoluteTime(NULL, df, CFAbsoluteTimeGetCurrent());
|
|
CFRelease(df);
|
|
if (str != NULL)
|
|
{
|
|
LoggerMessageAddString(encoder, str, PART_KEY_MESSAGE);
|
|
CFRelease(str);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)text, PART_KEY_MESSAGE);
|
|
}
|
|
LoggerMessageFinalize(encoder);
|
|
LoggerPushMessageToQueue(logger, encoder);
|
|
CFRelease(encoder);
|
|
}
|
|
else
|
|
{
|
|
LOGGERDBG2(CFSTR("-> failed creating encoder"));
|
|
}
|
|
}
|
|
}
|
|
|
|
void LogMarker(NSString *text)
|
|
{
|
|
LogMarkerTo(NULL, text);
|
|
}
|