| // 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 |