diff --git a/SignalServiceKit/src/Messages/TSMessagesManager.m b/SignalServiceKit/src/Messages/TSMessagesManager.m index d7fc2819d..425a400dc 100644 --- a/SignalServiceKit/src/Messages/TSMessagesManager.m +++ b/SignalServiceKit/src/Messages/TSMessagesManager.m @@ -42,19 +42,19 @@ NS_ASSUME_NONNULL_BEGIN -#define kOWSProdAssertParameterEnvelopeIsLegacy @"envelope_is_legacy" -#define kOWSProdAssertParameterEnvelopeHasContent @"has_content" -#define kOWSProdAssertParameterEnvelopeDescription @"envelope_description" -#define kOWSProdAssertParameterEnvelopeEncryptedLength @"encrypted_length" +#define kOWSAnalyticsParameterEnvelopeIsLegacy @"envelope_is_legacy" +#define kOWSAnalyticsParameterEnvelopeHasContent @"has_content" +#define kOWSAnalyticsParameterEnvelopeDescription @"envelope_description" +#define kOWSAnalyticsParameterEnvelopeEncryptedLength @"encrypted_length" #define AnalyticsParametersFromEnvelope(__envelope) \ ^{ \ NSData *__encryptedData = __envelope.hasContent ? __envelope.content : __envelope.legacyMessage; \ return (@{ \ - kOWSProdAssertParameterEnvelopeIsLegacy : @(__envelope.hasLegacyMessage), \ - kOWSProdAssertParameterEnvelopeHasContent : @(__envelope.hasContent), \ - kOWSProdAssertParameterEnvelopeDescription : [self descriptionForEnvelopeType:__envelope], \ - kOWSProdAssertParameterEnvelopeEncryptedLength : @(__encryptedData.length), \ + kOWSAnalyticsParameterEnvelopeIsLegacy : @(__envelope.hasLegacyMessage), \ + kOWSAnalyticsParameterEnvelopeHasContent : @(__envelope.hasContent), \ + kOWSAnalyticsParameterEnvelopeDescription : [self descriptionForEnvelopeType:__envelope], \ + kOWSAnalyticsParameterEnvelopeEncryptedLength : @(__encryptedData.length), \ }); \ } diff --git a/SignalServiceKit/src/Storage/TSStorageManager.m b/SignalServiceKit/src/Storage/TSStorageManager.m index 8475ec405..cea4330cc 100644 --- a/SignalServiceKit/src/Storage/TSStorageManager.m +++ b/SignalServiceKit/src/Storage/TSStorageManager.m @@ -121,14 +121,16 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; // The best we can try to do is to discard the current database // and behave like a clean install. - OWSProdError(@"storage_error_could_not_load_database"); + // NOTE: This analytics event will never be delivered, since the database isn't working. + OWSProdCritical(@"storage_error_could_not_load_database"); // Try to reset app by deleting database. // Disabled resetting storage until we have better data on why this happens. // [self resetSignalStorage]; if (![self tryToLoadDatabase]) { - OWSProdError(@"storage_error_could_not_load_database_second_attempt"); + // NOTE: This analytics event will never be delivered, since the database isn't working. + OWSProdCritical(@"storage_error_could_not_load_database_second_attempt"); [NSException raise:TSStorageManagerExceptionNameNoDatabase format:@"Failed to initialize database."]; } @@ -187,9 +189,9 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; OWSProdErrorWParams(@"storage_error_deserialization", ^{ return (@{ @"collection" : collection, - kOWSProdAssertParameterNSExceptionName : exception.name, - kOWSProdAssertParameterNSExceptionReason : exception.reason, - kOWSProdAssertParameterNSExceptionClassName : NSStringFromClass([exception class]), + kOWSAnalyticsParameterNSExceptionName : exception.name, + kOWSAnalyticsParameterNSExceptionReason : exception.reason, + kOWSAnalyticsParameterNSExceptionClassName : NSStringFromClass([exception class]), }); }); @throw exception; @@ -260,7 +262,8 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; BOOL success = [ressourceURL setResourceValues:resourcesAttrs error:&error]; if (error || !success) { - OWSProdErrorWNSError(@"storage_error_file_protecion", error); + // NOTE: This analytics event will never be delivered, since the database isn't working. + OWSProdCriticalWNSError(@"storage_error_file_protection", error); return; } } @@ -358,7 +361,8 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; BOOL shouldHavePassword = [NSFileManager.defaultManager fileExistsAtPath:[self dbPath]]; if (shouldHavePassword) { - OWSProdError(@"storage_error_could_not_load_database_second_attempt"); + // NOTE: This analytics event will never be delivered, since the database isn't working. + OWSProdCritical(@"storage_error_could_not_load_database_second_attempt"); } // Try to reset app by deleting database. @@ -377,7 +381,8 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; NSError *keySetError; [SAMKeychain setPassword:newDBPassword forService:keychainService account:keychainDBPassAccount error:&keySetError]; if (keySetError) { - OWSProdErrorWNSError(@"storage_error_could_not_store_database_password", keySetError); + // NOTE: This analytics event will never be delivered, since the database isn't working. + OWSProdCriticalWNSError(@"storage_error_could_not_store_database_password", keySetError); [self deletePasswordFromKeychain]; diff --git a/SignalServiceKit/src/Util/OWSAnalytics.h b/SignalServiceKit/src/Util/OWSAnalytics.h index 10714eeaa..3719fcade 100755 --- a/SignalServiceKit/src/Util/OWSAnalytics.h +++ b/SignalServiceKit/src/Util/OWSAnalytics.h @@ -6,23 +6,17 @@ NS_ASSUME_NONNULL_BEGIN // TODO: We probably don't need all of these levels. typedef NS_ENUM(NSUInteger, OWSAnalyticsSeverity) { - OWSAnalyticsSeverityDebug = 0, + // Info events are routine. + // + // It's safe to discard a large fraction of these events. OWSAnalyticsSeverityInfo = 1, - OWSAnalyticsSeverityWarn = 2, + // Error events should never be discarded. OWSAnalyticsSeverityError = 3, - // I suspect we'll stage the development of our analytics, - // initially building only a minimal solution: an endpoint which - // ignores most requests, and sends only the highest-severity - // events as email to developers. - // - // This "critical" level of severity is intended for that purpose (for now). + // Critical events should never be discarded. // - // We might want to have an additional level of severity for - // critical (crashing) bugs that occur during app startup. These - // events should be sent to the service immediately and the app - // should block until that request completes. - OWSAnalyticsSeverityCritical = 4, - OWSAnalyticsSeverityOff = 5 + // Additionally, to avoid losing critical events they should + // be persisted synchronously. + OWSAnalyticsSeverityCritical = 4 }; // This is a placeholder. We don't yet serialize or transmit analytics events. @@ -54,13 +48,32 @@ typedef NS_ENUM(NSUInteger, OWSAnalyticsSeverity) { typedef NSDictionary *_Nonnull (^OWSProdAssertParametersBlock)(); -#define kOWSProdAssertParameterDescription @"description" -#define kOWSProdAssertParameterNSErrorDomain @"nserror_domain" -#define kOWSProdAssertParameterNSErrorCode @"nserror_code" -#define kOWSProdAssertParameterNSErrorDescription @"nserror_description" -#define kOWSProdAssertParameterNSExceptionName @"nsexception_name" -#define kOWSProdAssertParameterNSExceptionReason @"nsexception_reason" -#define kOWSProdAssertParameterNSExceptionClassName @"nsexception_classname" +#define kOWSAnalyticsParameterDescription @"description" +#define kOWSAnalyticsParameterNSErrorDomain @"nserror_domain" +#define kOWSAnalyticsParameterNSErrorCode @"nserror_code" +#define kOWSAnalyticsParameterNSErrorDescription @"nserror_description" +#define kOWSAnalyticsParameterNSExceptionName @"nsexception_name" +#define kOWSAnalyticsParameterNSExceptionReason @"nsexception_reason" +#define kOWSAnalyticsParameterNSExceptionClassName @"nsexception_classname" + +#define AnalyticsParametersFromNSError(__nserror) \ + ^{ \ + return (@{ \ + kOWSAnalyticsParameterNSErrorDomain : (__nserror.domain ?: @"unknown"), \ + kOWSAnalyticsParameterNSErrorCode : @(__nserror.code), \ + kOWSAnalyticsParameterNSErrorDescription : (__nserror.description ?: @"unknown"), \ + }); \ + } + +#define AnalyticsParametersFromNSException(__exception) \ + ^{ \ + return (@{ \ + kOWSAnalyticsParameterNSExceptionName : (__exception.name ?: @"unknown"), \ + kOWSAnalyticsParameterNSExceptionReason : (__exception.reason ?: @"unknown"), \ + kOWSAnalyticsParameterNSExceptionClassName : \ + (__exception ? NSStringFromClass([__exception class]) : @"unknown"), \ + }); \ + } // These methods should be used to assert errors for which we want to fire analytics events. // @@ -116,25 +129,6 @@ typedef NSDictionary *_Nonnull (^OWSProdAssertParametersBlock)() #define OWSProdCFail(__analyticsEventName) OWSProdCFailWParams(__analyticsEventName, nil) -#define AnalyticsParametersFromNSError(__nserror) \ - ^{ \ - return (@{ \ - kOWSProdAssertParameterNSErrorDomain : (__nserror.domain ?: @"unknown"), \ - kOWSProdAssertParameterNSErrorCode : @(__nserror.code), \ - kOWSProdAssertParameterNSErrorDescription : (__nserror.description ?: @"unknown"), \ - }); \ - } - -#define AnalyticsParametersFromNSException(__exception) \ - ^{ \ - return (@{ \ - kOWSProdAssertParameterNSExceptionName : (__exception.name ?: @"unknown"), \ - kOWSProdAssertParameterNSExceptionReason : (__exception.reason ?: @"unknown"), \ - kOWSProdAssertParameterNSExceptionClassName : \ - (__exception ? NSStringFromClass([__exception class]) : @"unknown"), \ - }); \ - } - #define OWSProdFailWNSError(__analyticsEventName, __nserror) \ { \ DDLogError(@"%s:%d %@: %@", __PRETTY_FUNCTION__, __LINE__, __analyticsEventName, __nserror.debugDescription); \ @@ -147,28 +141,32 @@ typedef NSDictionary *_Nonnull (^OWSProdAssertParametersBlock)() OWSProdFailWParams(__analyticsEventName, AnalyticsParametersFromNSException(__exception)) \ } +#define OWSProdCFail(__analyticsEventName) OWSProdCFailWParams(__analyticsEventName, nil) + #define OWSProdEventWParams(__severityLevel, __analyticsEventName, __parametersBlock) \ { \ NSDictionary *__eventParameters \ = (__parametersBlock ? ((OWSProdAssertParametersBlock)__parametersBlock)() : nil); \ [OWSAnalytics logEvent:__analyticsEventName \ - severity:OWSAnalyticsSeverityCritical \ + severity:__severityLevel \ parameters:__eventParameters \ location:__PRETTY_FUNCTION__ \ line:__LINE__]; \ } -#define OWSProdErrorWParams(__analyticsEventName, __parametersBlock) \ - OWSProdEventWParams(OWSAnalyticsSeverityCritical, __analyticsEventName, __parametersBlock) - -#define OWSProdError(__analyticsEventName) OWSProdEventWParams(OWSAnalyticsSeverityCritical, __analyticsEventName, nil) +#pragma mark - Info Events #define OWSProdInfoWParams(__analyticsEventName, __parametersBlock) \ OWSProdEventWParams(OWSAnalyticsSeverityInfo, __analyticsEventName, __parametersBlock) #define OWSProdInfo(__analyticsEventName) OWSProdEventWParams(OWSAnalyticsSeverityInfo, __analyticsEventName, nil) -#define OWSProdCFail(__analyticsEventName) OWSProdCFailWParams(__analyticsEventName, nil) +#pragma mark - Error Events + +#define OWSProdErrorWParams(__analyticsEventName, __parametersBlock) \ + OWSProdEventWParams(OWSAnalyticsSeverityError, __analyticsEventName, __parametersBlock) + +#define OWSProdError(__analyticsEventName) OWSProdEventWParams(OWSAnalyticsSeverityError, __analyticsEventName, nil) #define OWSProdErrorWNSError(__analyticsEventName, __nserror) \ { \ @@ -182,4 +180,24 @@ typedef NSDictionary *_Nonnull (^OWSProdAssertParametersBlock)() OWSProdErrorWParams(__analyticsEventName, AnalyticsParametersFromNSException(__exception)) \ } +#pragma mark - Critical Events + +#define OWSProdCriticalWParams(__analyticsEventName, __parametersBlock) \ + OWSProdEventWParams(OWSAnalyticsSeverityCritical, __analyticsEventName, __parametersBlock) + +#define OWSProdCritical(__analyticsEventName) \ + OWSProdEventWParams(OWSAnalyticsSeverityCritical, __analyticsEventName, nil) + +#define OWSProdCriticalWNSError(__analyticsEventName, __nserror) \ + { \ + DDLogError(@"%s:%d %@: %@", __PRETTY_FUNCTION__, __LINE__, __analyticsEventName, __nserror.debugDescription); \ + OWSProdCriticalWParams(__analyticsEventName, AnalyticsParametersFromNSError(__nserror)) \ + } + +#define OWSProdCriticalWNSException(__analyticsEventName, __exception) \ + { \ + DDLogError(@"%s:%d %@: %@", __PRETTY_FUNCTION__, __LINE__, __analyticsEventName, __exception); \ + OWSProdCriticalWParams(__analyticsEventName, AnalyticsParametersFromNSException(__exception)) \ + } + NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSAnalytics.m b/SignalServiceKit/src/Util/OWSAnalytics.m index 043a008c3..eda5606db 100755 --- a/SignalServiceKit/src/Util/OWSAnalytics.m +++ b/SignalServiceKit/src/Util/OWSAnalytics.m @@ -223,7 +223,7 @@ const int kOWSAnalytics_DiscardFrequency = 0; return (long)round(pow(10, floor(log10(value)))); } -- (void)addEvent:(NSString *)eventName properties:(NSDictionary *)properties +- (void)addEvent:(NSString *)eventName async:(BOOL)async properties:(NSDictionary *)properties { OWSAssert(eventName.length > 0); @@ -234,7 +234,7 @@ const int kOWSAnalytics_DiscardFrequency = 0; } #ifndef NO_SIGNAL_ANALYTICS - dispatch_async(self.serialQueue, ^{ + void (^writeEvent)() = ^{ // Add super properties. NSMutableDictionary *eventProperties = (properties ? [properties mutableCopy] : [NSMutableDictionary new]); [eventProperties addEntriesFromDictionary:self.eventSuperProperties]; @@ -250,12 +250,18 @@ const int kOWSAnalytics_DiscardFrequency = 0; DDLogError(@"%@ Event queue overflow.", self.tag); return; } - + [transaction setObject:eventDictionary forKey:eventKey inCollection:kOWSAnalytics_EventsCollection]; }]; [self tryToSyncEvents]; - }); + }; + + if (async) { + dispatch_async(self.serialQueue, writeEvent); + } else { + dispatch_sync(self.serialQueue, writeEvent); + } #endif } @@ -277,18 +283,11 @@ const int kOWSAnalytics_DiscardFrequency = 0; DDLogFlag logFlag; BOOL async = YES; switch (severity) { - case OWSAnalyticsSeverityDebug: - logFlag = DDLogFlagDebug; - break; case OWSAnalyticsSeverityInfo: logFlag = DDLogFlagInfo; break; - case OWSAnalyticsSeverityWarn: - logFlag = DDLogFlagWarning; - break; case OWSAnalyticsSeverityError: logFlag = DDLogFlagError; - async = NO; break; case OWSAnalyticsSeverityCritical: logFlag = DDLogFlagError; @@ -313,7 +312,7 @@ const int kOWSAnalytics_DiscardFrequency = 0; NSMutableDictionary *eventProperties = (parameters ? [parameters mutableCopy] : [NSMutableDictionary new]); eventProperties[@"event_location"] = [NSString stringWithFormat:@"%s:%d", location, line]; - [self addEvent:eventName properties:eventProperties]; + [self addEvent:eventName async:async properties:eventProperties]; } #pragma mark - Logging