Merge branch 'charlesmchen/analytics5'

pull/1/head
Matthew Chen 7 years ago
commit 8236f05454

@ -212,7 +212,7 @@ NS_ASSUME_NONNULL_BEGIN
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
if (response.statusCode == 413) {
failure(OWSErrorWithCodeDescription(
OWSErrorCodeContactsUpdaterRateLimit, OWSSignalServiceKitErrorDomain));
OWSErrorCodeContactsUpdaterRateLimit, @"Contacts Intersection Rate Limit"));
} else {
failure(error);
}

@ -88,8 +88,8 @@ uint32_t const OWSDevicePrimaryDeviceId = 1;
}
}
*success = NO;
*error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecodeJson,
[NSString stringWithFormat:@"unable to decode date from %@", value]);
DDLogError(@"%@ unable to decode date from %@", self.tag, value);
*error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecodeJson, @"Unable to decode date from %@");
return nil;
}
reverseBlock:^id(id value, BOOL *success, NSError **error) {
@ -101,8 +101,8 @@ uint32_t const OWSDevicePrimaryDeviceId = 1;
return result;
}
}
*error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToEncodeJson,
[NSString stringWithFormat:@"unable to encode date from %@", value]);
DDLogError(@"%@ unable to encode date from %@", self.tag, value);
*error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToEncodeJson, @"Unable to encode date");
*success = NO;
return nil;
}];
@ -178,6 +178,18 @@ uint32_t const OWSDevicePrimaryDeviceId = 1;
return self.deviceId == device.deviceId;
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END

@ -42,22 +42,26 @@
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 kOWSAnalyticsParameterEnvelopeType @"envelope_type"
#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), \
kOWSAnalyticsParameterEnvelopeType : [self descriptionForEnvelopeType:__envelope], \
kOWSAnalyticsParameterEnvelopeEncryptedLength : @(__encryptedData.length), \
}); \
}
// The debug logs can be more verbose than the analytics events.
//
// In this case `descriptionForEnvelope` is valuable enough to
// log but too dangerous to include in the analytics event.
#define OWSProdErrorWEnvelope(__analyticsEventName, __envelope) \
{ \
DDLogError(@"%s:%d %@: %@", \

@ -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];

@ -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<NSString *, id> *_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"
// We don't include the error description because it may have PII.
#define AnalyticsParametersFromNSError(__nserror) \
^{ \
return (@{ \
kOWSAnalyticsParameterNSErrorDomain : (__nserror.domain ?: @"unknown"), \
kOWSAnalyticsParameterNSErrorCode : @(__nserror.code), \
}); \
}
#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,70 +129,95 @@ typedef NSDictionary<NSString *, id> *_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"), \
}); \
}
// The debug logs can be more verbose than the analytics events.
//
// In this case `debugDescription` is valuable enough to
// log but too dangerous to include in the analytics event.
#define OWSProdFailWNSError(__analyticsEventName, __nserror) \
{ \
DDLogError(@"%s:%d %@: %@", __PRETTY_FUNCTION__, __LINE__, __analyticsEventName, __nserror.debugDescription); \
OWSProdFailWParams(__analyticsEventName, AnalyticsParametersFromNSError(__nserror)) \
}
// The debug logs can be more verbose than the analytics events.
//
// In this case `exception` is valuable enough to
// log but too dangerous to include in the analytics event.
#define OWSProdFailWNSException(__analyticsEventName, __exception) \
{ \
DDLogError(@"%s:%d %@: %@", __PRETTY_FUNCTION__, __LINE__, __analyticsEventName, __exception); \
OWSProdFailWParams(__analyticsEventName, AnalyticsParametersFromNSException(__exception)) \
}
#define OWSProdCFail(__analyticsEventName) OWSProdCFailWParams(__analyticsEventName, nil)
#define OWSProdEventWParams(__severityLevel, __analyticsEventName, __parametersBlock) \
{ \
NSDictionary<NSString *, id> *__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)
// The debug logs can be more verbose than the analytics events.
//
// In this case `debugDescription` is valuable enough to
// log but too dangerous to include in the analytics event.
#define OWSProdErrorWNSError(__analyticsEventName, __nserror) \
{ \
DDLogError(@"%s:%d %@: %@", __PRETTY_FUNCTION__, __LINE__, __analyticsEventName, __nserror.debugDescription); \
OWSProdErrorWParams(__analyticsEventName, AnalyticsParametersFromNSError(__nserror)) \
}
// The debug logs can be more verbose than the analytics events.
//
// In this case `exception` is valuable enough to
// log but too dangerous to include in the analytics event.
#define OWSProdErrorWNSException(__analyticsEventName, __exception) \
{ \
DDLogError(@"%s:%d %@: %@", __PRETTY_FUNCTION__, __LINE__, __analyticsEventName, __exception); \
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)) \
}
// The debug logs can be more verbose than the analytics events.
//
// In this case `exception` is valuable enough to
// log but too dangerous to include in the analytics event.
#define OWSProdCriticalWNSException(__analyticsEventName, __exception) \
{ \
DDLogError(@"%s:%d %@: %@", __PRETTY_FUNCTION__, __LINE__, __analyticsEventName, __exception); \
OWSProdCriticalWParams(__analyticsEventName, AnalyticsParametersFromNSException(__exception)) \
}
NS_ASSUME_NONNULL_END

@ -124,15 +124,15 @@ const int kOWSAnalytics_DiscardFrequency = 0;
- (void)tryToSyncEvents
{
// Don't try to sync if:
//
// * There's no network available.
// * There's already a sync request in flight.
if (!self.reachability.isReachable || self.hasRequestInFlight) {
return;
}
dispatch_async(self.serialQueue, ^{
// Don't try to sync if:
//
// * There's no network available.
// * There's already a sync request in flight.
if (!self.reachability.isReachable || self.hasRequestInFlight) {
return;
}
__block NSString *firstEventKey = nil;
__block NSDictionary *firstEventDictionary = nil;
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
@ -157,6 +157,14 @@ const int kOWSAnalytics_DiscardFrequency = 0;
DDLogDebug(@"%@ trying to deliver event: %@", self.tag, firstEventKey);
self.hasRequestInFlight = YES;
__block UIBackgroundTaskIdentifier task;
task = [UIApplication.sharedApplication beginBackgroundTaskWithExpirationHandler:^{
self.hasRequestInFlight = NO;
[UIApplication.sharedApplication endBackgroundTask:task];
}];
// Until we integrate with an analytics platform, behave as though all event delivery succeeds.
dispatch_async(dispatch_get_main_queue(), ^{
self.hasRequestInFlight = NO;
@ -169,6 +177,8 @@ const int kOWSAnalytics_DiscardFrequency = 0;
}];
}
[UIApplication.sharedApplication endBackgroundTask:task];
// Wait a second between network requests / retries.
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@ -223,7 +233,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 +244,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 +260,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 +293,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 +322,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

Loading…
Cancel
Save