From bde1887b28fe54ceb3bc6830038fe385763f4f4c Mon Sep 17 00:00:00 2001 From: Dhilan007 Date: Mon, 30 Jun 2014 00:30:46 +0800 Subject: [PATCH] issue #4892:Implement logical codes of game controller --- external/nslog/CCNSLog.h | 24 + external/nslog/ios/CCNSLog.mm | 27 + external/nslog/ios/LoggerClient.h | 248 +++ external/nslog/ios/LoggerClient.m | 2866 +++++++++++++++++++++++++++++ external/nslog/ios/LoggerCommon.h | 113 ++ external/nslog/ios/NSLogger.h | 92 + 6 files changed, 3370 insertions(+) create mode 100644 external/nslog/CCNSLog.h create mode 100644 external/nslog/ios/CCNSLog.mm create mode 100644 external/nslog/ios/LoggerClient.h create mode 100644 external/nslog/ios/LoggerClient.m create mode 100644 external/nslog/ios/LoggerCommon.h create mode 100644 external/nslog/ios/NSLogger.h diff --git a/external/nslog/CCNSLog.h b/external/nslog/CCNSLog.h new file mode 100644 index 0000000000..e371bfa44d --- /dev/null +++ b/external/nslog/CCNSLog.h @@ -0,0 +1,24 @@ +// +// MyLog.h +// cocos2d_libs +// +// Created by James Chen on 4/23/14. +// +// + +#ifndef __cocos2d_libs__MyLog__ +#define __cocos2d_libs__MyLog__ + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + +extern void CCNSLog(const char* file, int line, const char* function, const char* format, ...); + +#define CCNSLOG(format, ...) CCNSLog(__FILE__, __LINE__, __FUNCTION__, format, ##__VA_ARGS__, nullptr) + +#else + +#define CCNSLOG CCLOG + +#endif + +#endif /* defined(__cocos2d_libs__MyLog__) */ diff --git a/external/nslog/ios/CCNSLog.mm b/external/nslog/ios/CCNSLog.mm new file mode 100644 index 0000000000..9583db3eaf --- /dev/null +++ b/external/nslog/ios/CCNSLog.mm @@ -0,0 +1,27 @@ +// +// MyLog.cpp +// cocos2d_libs +// +// Created by James Chen on 4/23/14. +// +// + +#include "CCNSLog.h" + +#import "NSLogger.h" + + +void CCNSLog(const char* file, int line, const char* function, const char* format, ...) +{ + va_list args; + va_start(args, format); + + char buf[1024 * 16] = {0}; + vsprintf(buf, format, args); + + va_end(args); + + LogMessageF(file, line, function, @"cocos2d-x", 0, [NSString stringWithUTF8String:buf], nullptr); + + // LoggerFlush(LoggerGetDefaultLogger(), YES); +} diff --git a/external/nslog/ios/LoggerClient.h b/external/nslog/ios/LoggerClient.h new file mode 100644 index 0000000000..3321f7126a --- /dev/null +++ b/external/nslog/ios/LoggerClient.h @@ -0,0 +1,248 @@ +/* + * LoggerClient.h + * + * version 1.5-RC2 22-NOV-2013 + * + * 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 +#import +#import +#import +#import +#import +#import +#if !TARGET_OS_IPHONE +#import +#endif + +// This define is here so that user application can test whether NSLogger Client is +// being included in the project, and potentially configure their macros accordingly +#define NSLOGGER_WAS_HERE 1 + +// Set this to 0 if you absolutely NOT want any access to Cocoa (Objective-C, NS* calls) +// We need a couple ones to reliably obtain the thread number and device information +// Note that since we need NSAutoreleasePool when using Cocoa in the logger's worker thread, +// we need to put Cocoa in multithreading mode. Also, ALLOW_COCOA_USE allows the client code +// to use NSLog()-style message formatting (less verbose than CFShow()-style) through the +// use of -[NSString stringWithFormat:arguments:] +#define ALLOW_COCOA_USE 1 + +/* ----------------------------------------------------------------- + * Logger option flags & default options + * ----------------------------------------------------------------- + */ +enum { + kLoggerOption_LogToConsole = 0x01, + kLoggerOption_BufferLogsUntilConnection = 0x02, + kLoggerOption_BrowseBonjour = 0x04, + kLoggerOption_BrowseOnlyLocalDomain = 0x08, + kLoggerOption_UseSSL = 0x10, + kLoggerOption_CaptureSystemConsole = 0x20 +}; + +#define LOGGER_DEFAULT_OPTIONS (kLoggerOption_BufferLogsUntilConnection | \ + kLoggerOption_BrowseBonjour | \ + kLoggerOption_BrowseOnlyLocalDomain | \ + kLoggerOption_UseSSL | \ + kLoggerOption_CaptureSystemConsole) + +/* ----------------------------------------------------------------- + * Structure defining a Logger + * ----------------------------------------------------------------- + */ +typedef struct +{ + CFStringRef bufferFile; // If non-NULL, all buffering is done to the specified file instead of in-memory + CFStringRef host; // Viewer host to connect to (instead of using Bonjour) + UInt32 port; // port on the viewer host + + CFMutableArrayRef bonjourServiceBrowsers; // Active service browsers + CFMutableArrayRef bonjourServices; // Services being tried + CFNetServiceBrowserRef bonjourDomainBrowser; // Domain browser + + CFMutableArrayRef logQueue; // Message queue + pthread_mutex_t logQueueMutex; + pthread_cond_t logQueueEmpty; + + dispatch_once_t workerThreadInit; // Use this to ensure creation of the worker thread is ever done only once for a given logger + pthread_t workerThread; // The worker thread responsible for Bonjour resolution, connection and logs transmission + CFRunLoopSourceRef messagePushedSource; // A message source that fires on the worker thread when messages are available for send + CFRunLoopSourceRef bufferFileChangedSource; // A message source that fires on the worker thread when the buffer file configuration changes + CFRunLoopSourceRef remoteOptionsChangedSource; // A message source that fires when option changes imply a networking strategy change (switch to/from Bonjour, direct host or file streaming) + + CFWriteStreamRef logStream; // The connected stream we're writing to + CFWriteStreamRef bufferWriteStream; // If bufferFile not NULL and we're not connected, points to a stream for writing log data + CFReadStreamRef bufferReadStream; // If bufferFile not NULL, points to a read stream that will be emptied prior to sending the rest of in-memory messages + + SCNetworkReachabilityRef reachability; // The reachability object we use to determine when the target host becomes reachable + SCNetworkReachabilityFlags reachabilityFlags; // Last known reachability flags - we use these to detect network transitions without network loss + CFRunLoopTimerRef reconnectTimer; // A timer to regularly check connection to the defined host, along with reachability for added reliability + + uint8_t *sendBuffer; // data waiting to be sent + NSUInteger sendBufferSize; + NSUInteger sendBufferUsed; // number of bytes of the send buffer currently in use + NSUInteger sendBufferOffset; // offset in sendBuffer to start sending at + + int32_t messageSeq; // sequential message number (added to each message sent) + + // settings + uint32_t options; // Flags, see enum above + CFStringRef bonjourServiceType; // leave NULL to use the default + CFStringRef bonjourServiceName; // leave NULL to use the first one available + + // internal state + BOOL targetReachable; // Set to YES when the Reachability target (host or internet) is deemed reachable + BOOL connected; // Set to YES once the write stream declares the connection open + volatile BOOL quit; // Set to YES to terminate the logger worker thread's runloop + BOOL incompleteSendOfFirstItem; // set to YES if we are sending the first item in the queue and it's bigger than what the buffer can hold +} Logger; + + +/* ----------------------------------------------------------------- + * LOGGING FUNCTIONS + * ----------------------------------------------------------------- + */ + +#ifdef __cplusplus +extern "C" { +#endif + +// Set the default logger which will be the one used when passing NULL for logge +extern void LoggerSetDefaultLogger(Logger *aLogger); + +// Get the default logger, create one if it does not exist +extern Logger *LoggerGetDefaultLogger(void); + +// Checks whether the default logger exists, returns it if YES, otherwise do NO create one +extern Logger *LoggerCheckDefaultLogger(void); + +// Initialize a new logger, set as default logger if this is the first one +// Options default to: +// - logging to console = NO +// - buffer until connection = YES +// - browse Bonjour = YES +// - browse only locally on Bonjour = YES +extern Logger* LoggerInit(void); + +// Set logger options if you don't want the default options (see above) +extern void LoggerSetOptions(Logger *logger, uint32_t options); + +// Set Bonjour logging names, so you can force the logger to use a specific service type +// or direct logs to the machine on your network which publishes a specific name +extern void LoggerSetupBonjour(Logger *logger, CFStringRef bonjourServiceType, CFStringRef bonjourServiceName); + +// Directly set the viewer host (hostname or IP address) and port we want to connect to. If set, LoggerStart() will +// try to connect there first before trying Bonjour +extern void LoggerSetViewerHost(Logger *logger, CFStringRef hostName, UInt32 port); + +// Configure the logger to use a local file for buffering, instead of memory. +// - If you initially set a buffer file after logging started but while a logger connection +// has not been acquired, the contents of the log queue will be written to the buffer file +// the next time a logging function is called, or when LoggerStop() is called. +// - If you want to change the buffering file after logging started, you should first +// call LoggerStop() the call LoggerSetBufferFile(). Note that all logs stored in the previous +// buffer file WON'T be transferred to the new file in this case. +extern void LoggerSetBufferFile(Logger *logger, CFStringRef absolutePath); + +// Activate the logger, try connecting. You can pass NULL to start the default logger, +// it will return a pointer to it. +extern Logger* LoggerStart(Logger *logger); + +//extern void LoggerConnectToHost(CFDataRef address, int port); + +// Deactivate and free the logger. +extern void LoggerStop(Logger *logger); + +// Pause the current thread until all messages from the logger have been transmitted +// this is useful to use before an assert() aborts your program. If waitForConnection is YES, +// LoggerFlush() will block even if the client is not currently connected to the desktop +// viewer. You should be using NO most of the time, but in some cases it can be useful. +extern void LoggerFlush(Logger *logger, BOOL waitForConnection); + +/* Logging functions. Each function exists in four versions: + * + * - one without a Logger instance (uses default logger) and without filename/line/function (no F suffix) + * - one without a Logger instance but with filename/line/function (F suffix) + * - one with a Logger instance (use a specific Logger) and without filename/line/function (no F suffix) + * - one with a Logger instance (use a specific Logger) and with filename/line/function (F suffix) + * + * The exception being the single LogMessageCompat() function which is designed to be a drop-in replacement for NSLog() + * + */ + +// Log a message, calling format compatible with NSLog +extern void LogMessageCompat(NSString *format, ...); + +// Log a message without any formatting (just log the given string) +extern void LogMessageRaw(NSString *message); +extern void LogMessageRawF(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *message); +extern void LogMessageRawToF(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *message); + +// Log a message. domain can be nil if default domain. +extern void LogMessage(NSString *domain, int level, NSString *format, ...) NS_FORMAT_FUNCTION(3,4); +extern void LogMessageF(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, ...) NS_FORMAT_FUNCTION(6,7); +extern void LogMessageTo(Logger *logger, NSString *domain, int level, NSString *format, ...) NS_FORMAT_FUNCTION(4,5); +extern void LogMessageToF(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, ...) NS_FORMAT_FUNCTION(7,8); + +// Log a message. domain can be nil if default domain (versions with va_list format args instead of ...) +extern void LogMessage_va(NSString *domain, int level, NSString *format, va_list args) NS_FORMAT_FUNCTION(3,0); +extern void LogMessageF_va(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, va_list args) NS_FORMAT_FUNCTION(6,0); +extern void LogMessageTo_va(Logger *logger, NSString *domain, int level, NSString *format, va_list args) NS_FORMAT_FUNCTION(4,0); +extern void LogMessageToF_va(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, va_list args) NS_FORMAT_FUNCTION(7,0); + +// Send binary data to remote logger +extern void LogData(NSString *domain, int level, NSData *data); +extern void LogDataF(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSData *data); +extern void LogDataTo(Logger *logger, NSString *domain, int level, NSData *data); +extern void LogDataToF(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSData *data); + +// Send image data to remote logger +extern void LogImageData(NSString *domain, int level, int width, int height, NSData *data); +extern void LogImageDataF(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, int width, int height, NSData *data); +extern void LogImageDataTo(Logger *logger, NSString *domain, int level, int width, int height, NSData *data); +extern void LogImageDataToF(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, int width, int height, NSData *data); + +// Mark the start of a block. This allows the remote logger to group blocks together +extern void LogStartBlock(NSString *format, ...) NS_FORMAT_FUNCTION(1,2); +extern void LogStartBlockTo(Logger *logger, NSString *format, ...) NS_FORMAT_FUNCTION(2,3); + +// Mark the end of a block +extern void LogEndBlock(void); +extern void LogEndBlockTo(Logger *logger); + +// Log a marker (text can be null) +extern void LogMarker(NSString *text); +extern void LogMarkerTo(Logger *logger, NSString *text); + +#ifdef __cplusplus +}; +#endif diff --git a/external/nslog/ios/LoggerClient.m b/external/nslog/ios/LoggerClient.m new file mode 100644 index 0000000000..0534ab58b8 --- /dev/null +++ b/external/nslog/ios/LoggerClient.m @@ -0,0 +1,2866 @@ +/* + * 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 +#import +#import + +#import "LoggerClient.h" +#import "LoggerCommon.h" + +#if !TARGET_OS_IPHONE + #import + #import + #import + #import +#elif ALLOW_COCOA_USE + #import +#endif +#import + +/* -------------------------------------------------------------------------------- + * 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(""), 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); +} diff --git a/external/nslog/ios/LoggerCommon.h b/external/nslog/ios/LoggerCommon.h new file mode 100644 index 0000000000..29c762da98 --- /dev/null +++ b/external/nslog/ios/LoggerCommon.h @@ -0,0 +1,113 @@ +/* + * LoggerCommon.h + * + * version 1.5-RC2 22-NOV-2013 + * + * Definitions common to NSLogger Viewer and NSLoggerClient + * for the binary messages format + * 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. + * + */ + +/* NSLogger native binary message format: + * Each message is a dictionary encoded in a compact format. All values are stored + * in network order (big endian). A message is made of several "parts", which are + * typed chunks of data, each with a specific purpose (partKey), data type (partType) + * and data size (partSize). + * + * uint32_t totalSize (total size for the whole message excluding this 4-byte count) + * uint16_t partCount (number of parts below) + * [repeat partCount times]: + * uint8_t partKey the part key + * uint8_t partType (string, binary, image, int16, int32, int64) + * uint32_t partSize (only for string, binary and image types, others are implicit) + * .. `partSize' data bytes + * + * Complete message is usually made of: + * - a PART_KEY_MESSAGE_TYPE (mandatory) which contains one of the LOGMSG_TYPE_* values + * - a PART_KEY_TIMESTAMP_S (mandatory) which is the timestamp returned by gettimeofday() (seconds from 01.01.1970 00:00) + * - a PART_KEY_TIMESTAMP_MS (optional) complement of the timestamp seconds, in milliseconds + * - a PART_KEY_TIMESTAMP_US (optional) complement of the timestamp seconds and milliseconds, in microseconds + * - a PART_KEY_THREAD_ID (mandatory) the ID of the user thread that produced the log entry + * - a PART_KEY_TAG (optional) a tag that helps categorizing and filtering logs from your application, and shows up in viewer logs + * - a PART_KEY_LEVEL (optional) a log level that helps filtering logs from your application (see as few or as much detail as you need) + * - a PART_KEY_MESSAGE which is the message text, binary data or image + * - a PART_KEY_MESSAGE_SEQ which is the message sequence number (message# sent by client) + * - a PART_KEY_FILENAME (optional) with the filename from which the log was generated + * - a PART_KEY_LINENUMBER (optional) the linenumber in the filename at which the log was generated + * - a PART_KEY_FUNCTIONNAME (optional) the function / method / selector from which the log was generated + * - if logging an image, PART_KEY_IMAGE_WIDTH and PART_KEY_IMAGE_HEIGHT let the desktop know the image size without having to actually decode it + */ + +// Constants for the "part key" field +#define PART_KEY_MESSAGE_TYPE 0 +#define PART_KEY_TIMESTAMP_S 1 // "seconds" component of timestamp +#define PART_KEY_TIMESTAMP_MS 2 // milliseconds component of timestamp (optional, mutually exclusive with PART_KEY_TIMESTAMP_US) +#define PART_KEY_TIMESTAMP_US 3 // microseconds component of timestamp (optional, mutually exclusive with PART_KEY_TIMESTAMP_MS) +#define PART_KEY_THREAD_ID 4 +#define PART_KEY_TAG 5 +#define PART_KEY_LEVEL 6 +#define PART_KEY_MESSAGE 7 +#define PART_KEY_IMAGE_WIDTH 8 // messages containing an image should also contain a part with the image size +#define PART_KEY_IMAGE_HEIGHT 9 // (this is mainly for the desktop viewer to compute the cell size without having to immediately decode the image) +#define PART_KEY_MESSAGE_SEQ 10 // the sequential number of this message which indicates the order in which messages are generated +#define PART_KEY_FILENAME 11 // when logging, message can contain a file name +#define PART_KEY_LINENUMBER 12 // as well as a line number +#define PART_KEY_FUNCTIONNAME 13 // and a function or method name + +// Constants for parts in LOGMSG_TYPE_CLIENTINFO +#define PART_KEY_CLIENT_NAME 20 +#define PART_KEY_CLIENT_VERSION 21 +#define PART_KEY_OS_NAME 22 +#define PART_KEY_OS_VERSION 23 +#define PART_KEY_CLIENT_MODEL 24 // For iPhone, device model (i.e 'iPhone', 'iPad', etc) +#define PART_KEY_UNIQUEID 25 // for remote device identification, part of LOGMSG_TYPE_CLIENTINFO + +// Area starting at which you may define your own constants +#define PART_KEY_USER_DEFINED 100 + +// Constants for the "partType" field +#define PART_TYPE_STRING 0 // Strings are stored as UTF-8 data +#define PART_TYPE_BINARY 1 // A block of binary data +#define PART_TYPE_INT16 2 +#define PART_TYPE_INT32 3 +#define PART_TYPE_INT64 4 +#define PART_TYPE_IMAGE 5 // An image, stored in PNG format + +// Data values for the PART_KEY_MESSAGE_TYPE parts +#define LOGMSG_TYPE_LOG 0 // A standard log message +#define LOGMSG_TYPE_BLOCKSTART 1 // The start of a "block" (a group of log entries) +#define LOGMSG_TYPE_BLOCKEND 2 // The end of the last started "block" +#define LOGMSG_TYPE_CLIENTINFO 3 // Information about the client app +#define LOGMSG_TYPE_DISCONNECT 4 // Pseudo-message on the desktop side to identify client disconnects +#define LOGMSG_TYPE_MARK 5 // Pseudo-message that defines a "mark" that users can place in the log flow + +// Default Bonjour service identifiers +#define LOGGER_SERVICE_TYPE_SSL CFSTR("_nslogger-ssl._tcp") +#define LOGGER_SERVICE_TYPE CFSTR("_nslogger._tcp") diff --git a/external/nslog/ios/NSLogger.h b/external/nslog/ios/NSLogger.h new file mode 100644 index 0000000000..f2721ca31f --- /dev/null +++ b/external/nslog/ios/NSLogger.h @@ -0,0 +1,92 @@ +/* + * NSLogger.h + * + * version 1.5-RC2 22-NOV-2013 + * + * 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 "LoggerClient.h" + + + +// Log level usual usage: +// Level 0: errors only! +// Level 1: important informations, app states… +// Level 2: less important logs, network requests… +// Level 3: network responses, datas and images… +// Level 4: really not important stuff. + + + +#if 0 + #define NSLog(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"NSLog", 0, __VA_ARGS__) + #define LoggerError(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Error", level, __VA_ARGS__) + #define LoggerApp(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"App", level, __VA_ARGS__) + #define LoggerView(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"View", level, __VA_ARGS__) + #define LoggerService(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Service", level, __VA_ARGS__) + #define LoggerModel(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Model", level, __VA_ARGS__) + #define LoggerData(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Data", level, __VA_ARGS__) + #define LoggerNetwork(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Network", level, __VA_ARGS__) + #define LoggerLocation(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Location", level, __VA_ARGS__) + #define LoggerPush(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Push", level, __VA_ARGS__) + #define LoggerFile(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"File", level, __VA_ARGS__) + #define LoggerSharing(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Sharing", level, __VA_ARGS__) + #define LoggerAd(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Ad and Stat", level, __VA_ARGS__) + +#else + #define NSLog(...) LogMessageCompat(__VA_ARGS__) + #define LoggerError(...) while(0} {} + #define LoggerApp(level, ...) while(0) {} + #define LoggerView(...) while(0) {} + #define LoggerService(...) while(0) {} + #define LoggerModel(...) while(0) {} + #define LoggerData(...) while(0) {} + #define LoggerNetwork(...) while(0) {} + #define LoggerLocation(...) while(0) {} + #define LoggerPush(...) while(0) {} + #define LoggerFile(...) while(0) {} + #define LoggerSharing(...) while(0) {} + #define LoggerAd(...) while(0) {} + +#endif + + + +// Starts the logger with the username defined in the build settings. +// The build setting NSLOGGER_BUILD_USERNAME is automatically configured when NSLogger is +// added to a project using CocoaPods. To use it, just add this macro call to your main() function. +#define LoggerStartForBuildUser() LoggerSetupBonjour(LoggerGetDefaultLogger(), NULL, CFSTR(xstr(NSLOGGER_BUILD_USERNAME))) + + + + + +