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