summaryrefslogtreecommitdiff
path: root/platform/ios/src/MGLMapboxEvents.m
diff options
context:
space:
mode:
authorJason Wray <jason@mapbox.com>2016-01-13 16:38:03 -0500
committerJason Wray <jason@mapbox.com>2016-01-20 18:57:43 -0500
commit4a3adddf3f4f833768f680d2d1e228c88f2e06e3 (patch)
tree2bd8c098631399678ddfb53f45934c533bc089ae /platform/ios/src/MGLMapboxEvents.m
parent27271768bd94068f53f58e030857922abc495555 (diff)
downloadqtlocation-mapboxgl-4a3adddf3f4f833768f680d2d1e228c88f2e06e3.tar.gz
[ios] add telemetry debug logging
You SHOULD NOT be using telemetry logging on any persons' devices who do not explicitly understand the privacy implications of handling location data.
Diffstat (limited to 'platform/ios/src/MGLMapboxEvents.m')
-rw-r--r--platform/ios/src/MGLMapboxEvents.m180
1 files changed, 176 insertions, 4 deletions
diff --git a/platform/ios/src/MGLMapboxEvents.m b/platform/ios/src/MGLMapboxEvents.m
index bde735f224..6654cc6ac5 100644
--- a/platform/ios/src/MGLMapboxEvents.m
+++ b/platform/ios/src/MGLMapboxEvents.m
@@ -22,6 +22,7 @@ NSString *const MGLEventTypeMapTap = @"map.click";
NSString *const MGLEventTypeMapDragEnd = @"map.dragend";
NSString *const MGLEventTypeLocation = @"location";
NSString *const MGLEventTypeVisit = @"visit";
+NSString *const MGLEventTypeLocalDebug = @"debug";
NSString *const MGLEventKeyLatitude = @"lat";
NSString *const MGLEventKeyLongitude = @"lng";
@@ -36,6 +37,7 @@ NSString *const MGLEventKeyEmailEnabled = @"enabled.email";
NSString *const MGLEventKeyGestureID = @"gesture";
NSString *const MGLEventKeyArrivalDate = @"arrivalDate";
NSString *const MGLEventKeyDepartureDate = @"departureDate";
+NSString *const MGLEventKeyLocalDebugDescription = @"debug.description";
NSString *const MGLEventGestureSingleTap = @"SingleTap";
NSString *const MGLEventGestureDoubleTap = @"DoubleTap";
@@ -151,6 +153,10 @@ const NSTimeInterval MGLFlushInterval = 60;
//
@property (nonatomic) dispatch_queue_t serialQueue;
+@property (atomic) BOOL canEnableDebugLogging;
+@property (nonatomic) dispatch_queue_t debugLogSerialQueue;
+@property (nonatomic) NSString *dateForDebugLogFile;
+
@end
@implementation MGLMapboxEvents {
@@ -164,6 +170,7 @@ const NSTimeInterval MGLFlushInterval = 60;
[[NSUserDefaults standardUserDefaults] registerDefaults:@{
@"MGLMapboxAccountType": accountTypeNumber ? accountTypeNumber : @0,
@"MGLMapboxMetricsEnabled": @YES,
+ @"MGLMapboxMetricsDebugLoggingEnabled": @NO,
}];
}
}
@@ -173,6 +180,15 @@ const NSTimeInterval MGLFlushInterval = 60;
[[NSUserDefaults standardUserDefaults] integerForKey:@"MGLMapboxAccountType"] == 0);
}
+- (BOOL)debugLoggingEnabled {
+ return (self.canEnableDebugLogging &&
+ [[NSUserDefaults standardUserDefaults] boolForKey:@"MGLMapboxMetricsDebugLoggingEnabled"]);
+}
+
++ (BOOL)debugLoggingEnabled {
+ return [[MGLMapboxEvents sharedManager] debugLoggingEnabled];
+}
+
// Must be called from the main thread. Only called internally.
//
- (instancetype) init {
@@ -237,7 +253,19 @@ const NSTimeInterval MGLFlushInterval = 60;
// Enable Battery Monitoring
[UIDevice currentDevice].batteryMonitoringEnabled = YES;
-
+
+ // Configure logging
+ if ([self isProbablyAppStoreBuild]) {
+ self.canEnableDebugLogging = NO;
+
+ if ([[NSUserDefaults standardUserDefaults] boolForKey:@"MGLMapboxMetricsDebugLoggingEnabled"]) {
+ NSLog(@"Telemetry logging is only enabled in non-app store builds.");
+ }
+ } else {
+ self.canEnableDebugLogging = YES;
+ }
+
+ // Watch for changes to telemetry settings by the user
__weak MGLMapboxEvents *weakSelf = self;
_userDefaultsObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification
object:nil
@@ -381,9 +409,9 @@ const NSTimeInterval MGLFlushInterval = 60;
_locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;
_locationManager.distanceFilter = 10;
_locationManager.delegate = self;
-
+
[_locationManager startUpdatingLocation];
-
+
// -[CLLocationManager startMonitoringVisits] is only available in iOS 8+.
if ([_locationManager respondsToSelector:@selector(startMonitoringVisits)]) {
[_locationManager startMonitoringVisits];
@@ -419,6 +447,11 @@ const NSTimeInterval MGLFlushInterval = 60;
// Flush
[strongSelf flush];
+
+ if ([strongSelf debugLoggingEnabled]) {
+ [strongSelf writeEventToLocalDebugLog:vevt];
+ }
+
});
}
@@ -458,7 +491,7 @@ const NSTimeInterval MGLFlushInterval = 60;
[evt setObject:strongSelf.instanceID forKey:@"instance"];
[evt setObject:strongSelf.data.vendorId forKey:@"vendorId"];
[evt setObject:strongSelf.appBundleId forKeyedSubscript:@"appBundleId"];
-
+
// mapbox-events-ios stock attributes
[evt setValue:strongSelf.data.model forKey:@"model"];
[evt setValue:strongSelf.data.iOSVersion forKey:@"operatingSystem"];
@@ -486,6 +519,10 @@ const NSTimeInterval MGLFlushInterval = 60;
// If this is first new event on queue start timer,
[strongSelf startTimer];
}
+
+ if ([strongSelf debugLoggingEnabled]) {
+ [strongSelf writeEventToLocalDebugLog:finalEvent];
+ }
});
}
@@ -521,6 +558,12 @@ const NSTimeInterval MGLFlushInterval = 60;
strongSelf.timer = nil;
}
});
+
+ if ([self debugLoggingEnabled]) {
+ [MGLMapboxEvents pushDebugEvent:MGLEventTypeLocalDebug withAttributes:@{
+ MGLEventKeyLocalDebugDescription: @"flush"
+ }];
+ }
}
// Can be called from any thread. Called implicitly from public
@@ -559,6 +602,13 @@ const NSTimeInterval MGLFlushInterval = 60;
}
}
}
+
+ if ([self debugLoggingEnabled]) {
+ [MGLMapboxEvents pushDebugEvent:MGLEventTypeLocalDebug withAttributes:@{
+ MGLEventKeyLocalDebugDescription: @"post",
+ @"debug.eventsCount": @(events.count)
+ }];
+ }
}
});
}
@@ -898,4 +948,126 @@ const NSTimeInterval MGLFlushInterval = 60;
}
+#pragma mark MGLMapboxEvents Debug
+
+// Can be called from any thread.
+//
++ (void) pushDebugEvent:(NSString *)event withAttributes:(MGLMapboxEventAttributes *)attributeDictionary {
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
+ [[MGLMapboxEvents sharedManager] pushDebugEvent:event withAttributes:attributeDictionary];
+ });
+}
+
+// Can be called from any thread. Called implicitly from public
+// use of +pushDebugEvent:withAttributes:.
+//
+- (void) pushDebugEvent:(NSString *)event withAttributes:(MGLMapboxEventAttributes *)attributeDictionary {
+ __weak MGLMapboxEvents *weakSelf = self;
+
+ if (![self debugLoggingEnabled] || !event) return;
+
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
+
+ MGLMapboxEvents *strongSelf = weakSelf;
+
+ if (!strongSelf) return;
+
+ MGLMutableMapboxEventAttributes *evt = [MGLMutableMapboxEventAttributes dictionaryWithDictionary:attributeDictionary];
+
+ [evt setObject:event forKey:@"event"];
+ [evt setObject:[strongSelf.rfc3339DateFormatter stringFromDate:[NSDate date]] forKey:@"created"];
+ [evt setValue:[strongSelf applicationState] forKey:@"applicationState"];
+ [evt setValue:@([[self class] isEnabled]) forKey:@"telemetryEnabled"];
+ [evt setObject:strongSelf.instanceID forKey:@"instance"];
+
+ // Make immutable version
+ MGLMapboxEventAttributes *finalEvent = [NSDictionary dictionaryWithDictionary:evt];
+
+ [strongSelf writeEventToLocalDebugLog:finalEvent];
+
+ });
+}
+
+- (void) writeEventToLocalDebugLog:(MGLMapboxEventAttributes *)event {
+ if (![self debugLoggingEnabled]) return;
+
+ NSLog(@"%@", [self stringForDebugEvent:event]);
+
+ if ( ! self.dateForDebugLogFile) {
+ NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
+ [dateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
+ [dateFormatter setTimeZone:[NSTimeZone systemTimeZone]];
+ self.dateForDebugLogFile = [dateFormatter stringFromDate:[NSDate date]];
+ }
+
+ if ( ! _debugLogSerialQueue) {
+ NSString *uniqueID = [[NSProcessInfo processInfo] globallyUniqueString];
+ _debugLogSerialQueue = dispatch_queue_create([[NSString stringWithFormat:@"%@.%@.events.debugLog", _appBundleId, uniqueID] UTF8String], DISPATCH_QUEUE_SERIAL);
+ }
+
+ dispatch_sync(_debugLogSerialQueue, ^{
+ if ([NSJSONSerialization isValidJSONObject:event]) {
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:event options:NSJSONWritingPrettyPrinted error:nil];
+ NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
+
+ jsonString = [jsonString stringByAppendingString:@",\n"];
+
+ NSString *logFilePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:[NSString stringWithFormat:@"telemetry_log-%@.json", self.dateForDebugLogFile]];
+
+ NSFileManager *fileManager = [[NSFileManager alloc] init];
+ if ([fileManager fileExistsAtPath:logFilePath]) {
+ NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
+ [fileHandle seekToEndOfFile];
+ [fileHandle writeData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]];
+ } else {
+ [fileManager createFileAtPath:logFilePath contents:[jsonString dataUsingEncoding:NSUTF8StringEncoding] attributes:@{ NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication }];
+ }
+ }
+
+ });
+}
+
+- (NSString *) stringForDebugEvent:(MGLMapboxEventAttributes *)event {
+ // redact potentially sensitive location details from system console log
+ if ([event[@"event"] isEqualToString:MGLEventTypeLocation] ||
+ [event[@"event"] isEqualToString:MGLEventTypeVisit]) {
+ MGLMutableMapboxEventAttributes *evt = [MGLMutableMapboxEventAttributes dictionaryWithDictionary:event];
+ [evt setObject:@"<redacted>" forKey:@"lat"];
+ [evt setObject:@"<redacted>" forKey:@"lng"];
+ event = evt;
+ }
+
+ return [NSString stringWithFormat:@"Mapbox Telemetry event %@", event];
+}
+
+- (BOOL) isProbablyAppStoreBuild {
+#if TARGET_IPHONE_SIMULATOR
+ return NO;
+#else
+ // BugshotKit by Marco Arment https://github.com/marcoarment/BugshotKit/
+ // Adapted from https://github.com/blindsightcorp/BSMobileProvision
+
+ NSString *binaryMobileProvision = [NSString stringWithContentsOfFile:[NSBundle.mainBundle pathForResource:@"embedded" ofType:@"mobileprovision"] encoding:NSISOLatin1StringEncoding error:NULL];
+ if (! binaryMobileProvision) return YES; // no provision
+
+ NSScanner *scanner = [NSScanner scannerWithString:binaryMobileProvision];
+ NSString *plistString;
+ if (! [scanner scanUpToString:@"<plist" intoString:nil] || ! [scanner scanUpToString:@"</plist>" intoString:&plistString]) return YES; // no XML plist found in provision
+ plistString = [plistString stringByAppendingString:@"</plist>"];
+
+ NSData *plistdata_latin1 = [plistString dataUsingEncoding:NSISOLatin1StringEncoding];
+ NSError *error = nil;
+ NSDictionary *mobileProvision = [NSPropertyListSerialization propertyListWithData:plistdata_latin1 options:NSPropertyListImmutable format:NULL error:&error];
+ if (error) return YES; // unknown plist format
+
+ if (! mobileProvision || ! mobileProvision.count) return YES; // no entitlements
+
+ if (mobileProvision[@"ProvisionsAllDevices"]) return NO; // enterprise provisioning
+
+ if (mobileProvision[@"ProvisionedDevices"] && [mobileProvision[@"ProvisionedDevices"] count]) return NO; // development or ad-hoc
+
+ return YES; // expected development/enterprise/ad-hoc entitlements not found
+#endif
+}
+
@end