|  | // Software License Agreement (BSD License) | 
|  | // | 
|  | // Copyright (c) 2010-2015, Deusty, LLC | 
|  | // All rights reserved. | 
|  | // | 
|  | // Redistribution and use of this software 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. | 
|  | // | 
|  | // * Neither the name of Deusty nor the names of its contributors may be used | 
|  | //   to endorse or promote products derived from this software without specific | 
|  | //   prior written permission of Deusty, LLC. | 
|  |  | 
|  | #import "DDASLLogCapture.h" | 
|  |  | 
|  | // Disable legacy macros | 
|  | #ifndef DD_LEGACY_MACROS | 
|  | #define DD_LEGACY_MACROS 0 | 
|  | #endif | 
|  |  | 
|  | #import "DDLog.h" | 
|  |  | 
|  | #include <asl.h> | 
|  | #include <notify.h> | 
|  | #include <notify_keys.h> | 
|  | #include <sys/time.h> | 
|  |  | 
|  | static BOOL _cancel = YES; | 
|  | static DDLogLevel _captureLevel = DDLogLevelVerbose; | 
|  |  | 
|  | #ifdef __IPHONE_8_0 | 
|  | #define DDASL_IOS_PIVOT_VERSION __IPHONE_8_0 | 
|  | #endif | 
|  | #ifdef __MAC_10_10 | 
|  | #define DDASL_OSX_PIVOT_VERSION __MAC_10_10 | 
|  | #endif | 
|  |  | 
|  | @implementation DDASLLogCapture | 
|  |  | 
|  | static aslmsg (*dd_asl_next)(aslresponse obj); | 
|  | static void (*dd_asl_release)(aslresponse obj); | 
|  |  | 
|  | + (void)initialize | 
|  | { | 
|  | #if (defined(DDASL_IOS_PIVOT_VERSION) && __IPHONE_OS_VERSION_MAX_ALLOWED >= DDASL_IOS_PIVOT_VERSION) || (defined(DDASL_OSX_PIVOT_VERSION) && __MAC_OS_X_VERSION_MAX_ALLOWED >= DDASL_OSX_PIVOT_VERSION) | 
|  | #if __IPHONE_OS_VERSION_MIN_REQUIRED < DDASL_IOS_PIVOT_VERSION || __MAC_OS_X_VERSION_MIN_REQUIRED < DDASL_OSX_PIVOT_VERSION | 
|  | #pragma GCC diagnostic push | 
|  | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | 
|  | // Building on falsely advertised SDK, targeting deprecated API | 
|  | dd_asl_next    = &aslresponse_next; | 
|  | dd_asl_release = &aslresponse_free; | 
|  | #pragma GCC diagnostic pop | 
|  | #else | 
|  | // Building on lastest, correct SDK, targeting latest API | 
|  | dd_asl_next    = &asl_next; | 
|  | dd_asl_release = &asl_release; | 
|  | #endif | 
|  | #else | 
|  | // Building on old SDKs, targeting deprecated API | 
|  | dd_asl_next    = &aslresponse_next; | 
|  | dd_asl_release = &aslresponse_free; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | + (void)start { | 
|  | // Ignore subsequent calls | 
|  | if (!_cancel) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | _cancel = NO; | 
|  |  | 
|  | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { | 
|  | [self captureAslLogs]; | 
|  | }); | 
|  | } | 
|  |  | 
|  | + (void)stop { | 
|  | _cancel = YES; | 
|  | } | 
|  |  | 
|  | + (DDLogLevel)captureLevel { | 
|  | return _captureLevel; | 
|  | } | 
|  |  | 
|  | + (void)setCaptureLevel:(DDLogLevel)level { | 
|  | _captureLevel = level; | 
|  | } | 
|  |  | 
|  | #pragma mark - Private methods | 
|  |  | 
|  | + (void)configureAslQuery:(aslmsg)query { | 
|  | const char param[] = "7";  // ASL_LEVEL_DEBUG, which is everything. We'll rely on regular DDlog log level to filter | 
|  |  | 
|  | asl_set_query(query, ASL_KEY_LEVEL, param, ASL_QUERY_OP_LESS_EQUAL | ASL_QUERY_OP_NUMERIC); | 
|  |  | 
|  | // Don't retrieve logs from our own DDASLLogger | 
|  | asl_set_query(query, kDDASLKeyDDLog, kDDASLDDLogValue, ASL_QUERY_OP_NOT_EQUAL); | 
|  |  | 
|  | #if !TARGET_OS_IPHONE || TARGET_SIMULATOR | 
|  | int processId = [[NSProcessInfo processInfo] processIdentifier]; | 
|  | char pid[16]; | 
|  | sprintf(pid, "%d", processId); | 
|  | asl_set_query(query, ASL_KEY_PID, pid, ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_NUMERIC); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | + (void)aslMessageReceived:(aslmsg)msg { | 
|  | const char* messageCString = asl_get( msg, ASL_KEY_MSG ); | 
|  | if ( messageCString == NULL ) | 
|  | return; | 
|  |  | 
|  | int flag; | 
|  | BOOL async; | 
|  |  | 
|  | const char* levelCString = asl_get(msg, ASL_KEY_LEVEL); | 
|  | switch (levelCString? atoi(levelCString) : 0) { | 
|  | // By default all NSLog's with a ASL_LEVEL_WARNING level | 
|  | case ASL_LEVEL_EMERG    : | 
|  | case ASL_LEVEL_ALERT    : | 
|  | case ASL_LEVEL_CRIT     : flag = DDLogFlagError;    async = NO;  break; | 
|  | case ASL_LEVEL_ERR      : flag = DDLogFlagWarning;  async = YES; break; | 
|  | case ASL_LEVEL_WARNING  : flag = DDLogFlagInfo;     async = YES; break; | 
|  | case ASL_LEVEL_NOTICE   : flag = DDLogFlagDebug;    async = YES; break; | 
|  | case ASL_LEVEL_INFO     : | 
|  | case ASL_LEVEL_DEBUG    : | 
|  | default                 : flag = DDLogFlagVerbose;  async = YES;  break; | 
|  | } | 
|  |  | 
|  | if (!(_captureLevel & flag)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | //  NSString * sender = [NSString stringWithCString:asl_get(msg, ASL_KEY_SENDER) encoding:NSUTF8StringEncoding]; | 
|  | NSString *message = @(messageCString); | 
|  |  | 
|  | const char* secondsCString = asl_get( msg, ASL_KEY_TIME ); | 
|  | const char* nanoCString = asl_get( msg, ASL_KEY_TIME_NSEC ); | 
|  | NSTimeInterval seconds = secondsCString ? strtod(secondsCString, NULL) : [NSDate timeIntervalSinceReferenceDate] - NSTimeIntervalSince1970; | 
|  | double nanoSeconds = nanoCString? strtod(nanoCString, NULL) : 0; | 
|  | NSTimeInterval totalSeconds = seconds + (nanoSeconds / 1e9); | 
|  |  | 
|  | NSDate *timeStamp = [NSDate dateWithTimeIntervalSince1970:totalSeconds]; | 
|  |  | 
|  | DDLogMessage *logMessage = [[DDLogMessage alloc]initWithMessage:message | 
|  | level:_captureLevel | 
|  | flag:flag | 
|  | context:0 | 
|  | file:@"DDASLLogCapture" | 
|  | function:0 | 
|  | line:0 | 
|  | tag:nil | 
|  | options:0 | 
|  | timestamp:timeStamp]; | 
|  |  | 
|  | [DDLog log:async message:logMessage]; | 
|  | } | 
|  |  | 
|  | + (void)captureAslLogs { | 
|  | @autoreleasepool | 
|  | { | 
|  | /* | 
|  | We use ASL_KEY_MSG_ID to see each message once, but there's no | 
|  | obvious way to get the "next" ID. To bootstrap the process, we'll | 
|  | search by timestamp until we've seen a message. | 
|  | */ | 
|  |  | 
|  | struct timeval timeval = { | 
|  | .tv_sec = 0 | 
|  | }; | 
|  | gettimeofday(&timeval, NULL); | 
|  | unsigned long long startTime = timeval.tv_sec; | 
|  | __block unsigned long long lastSeenID = 0; | 
|  |  | 
|  | /* | 
|  | syslogd posts kNotifyASLDBUpdate (com.apple.system.logger.message) | 
|  | through the notify API when it saves messages to the ASL database. | 
|  | There is some coalescing - currently it is sent at most twice per | 
|  | second - but there is no documented guarantee about this. In any | 
|  | case, there may be multiple messages per notification. | 
|  |  | 
|  | Notify notifications don't carry any payload, so we need to search | 
|  | for the messages. | 
|  | */ | 
|  | int notifyToken = 0;  // Can be used to unregister with notify_cancel(). | 
|  | notify_register_dispatch(kNotifyASLDBUpdate, ¬ifyToken, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(int token) | 
|  | { | 
|  | // At least one message has been posted; build a search query. | 
|  | @autoreleasepool | 
|  | { | 
|  | aslmsg query = asl_new(ASL_TYPE_QUERY); | 
|  | char stringValue[64]; | 
|  |  | 
|  | if (lastSeenID > 0) { | 
|  | snprintf(stringValue, sizeof stringValue, "%llu", lastSeenID); | 
|  | asl_set_query(query, ASL_KEY_MSG_ID, stringValue, ASL_QUERY_OP_GREATER | ASL_QUERY_OP_NUMERIC); | 
|  | } else { | 
|  | snprintf(stringValue, sizeof stringValue, "%llu", startTime); | 
|  | asl_set_query(query, ASL_KEY_TIME, stringValue, ASL_QUERY_OP_GREATER_EQUAL | ASL_QUERY_OP_NUMERIC); | 
|  | } | 
|  |  | 
|  | [self configureAslQuery:query]; | 
|  |  | 
|  | // Iterate over new messages. | 
|  | aslmsg msg; | 
|  | aslresponse response = asl_search(NULL, query); | 
|  |  | 
|  | while ((msg = dd_asl_next(response))) | 
|  | { | 
|  | [self aslMessageReceived:msg]; | 
|  |  | 
|  | // Keep track of which messages we've seen. | 
|  | lastSeenID = atoll(asl_get(msg, ASL_KEY_MSG_ID)); | 
|  | } | 
|  | dd_asl_release(response); | 
|  | asl_free(query); | 
|  |  | 
|  | if (_cancel) { | 
|  | notify_cancel(token); | 
|  | return; | 
|  | } | 
|  |  | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | @end |