summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE.md25
-rw-r--r--ios/app/MBXViewController.mm59
-rw-r--r--platform/ios/src/MGLMapboxEvents.h6
-rw-r--r--platform/ios/src/MGLMapboxEvents.m180
4 files changed, 249 insertions, 21 deletions
diff --git a/LICENSE.md b/LICENSE.md
index 095660f87a..e1d6f55fe5 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -885,3 +885,28 @@ freely, subject to the following restrictions:
Jean-loup Gailly Mark Adler
jloup@gzip.org madler@alumni.caltech.edu
+
+===========================================================================
+
+Mapbox GL uses portions of BugshotKit.
+
+The MIT License (MIT)
+
+Copyright (c) 2014 marcoarment
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/ios/app/MBXViewController.mm b/ios/app/MBXViewController.mm
index 57817d752b..6a2b0cc3fa 100644
--- a/ios/app/MBXViewController.mm
+++ b/ios/app/MBXViewController.mm
@@ -144,9 +144,8 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
delegate:self
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:nil
- otherButtonTitles:@"Reset North",
- @"Reset Position",
- @"Cycle debug options",
+ otherButtonTitles:@"Reset Position",
+ @"Cycle Debug Options",
@"Empty Memory",
@"Add 100 Points",
@"Add 1,000 Points",
@@ -156,6 +155,8 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
@"Add Custom Callout Point",
@"Remove Annotations",
@"Toggle Custom Style Layer",
+ @"Print Telemetry Logfile",
+ @"Delete Telemetry Logfile",
nil];
[sheet showFromBarButtonItem:self.navigationItem.leftBarButtonItem animated:YES];
@@ -165,33 +166,29 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
{
if (buttonIndex == actionSheet.firstOtherButtonIndex)
{
- [self.mapView resetNorth];
- }
- else if (buttonIndex == actionSheet.firstOtherButtonIndex + 1)
- {
[self.mapView resetPosition];
}
- else if (buttonIndex == actionSheet.firstOtherButtonIndex + 2)
+ else if (buttonIndex == actionSheet.firstOtherButtonIndex + 1)
{
[self.mapView cycleDebugOptions];
}
- else if (buttonIndex == actionSheet.firstOtherButtonIndex + 3)
+ else if (buttonIndex == actionSheet.firstOtherButtonIndex + 2)
{
[self.mapView emptyMemoryCache];
}
- else if (buttonIndex == actionSheet.firstOtherButtonIndex + 4)
+ else if (buttonIndex == actionSheet.firstOtherButtonIndex + 3)
{
[self parseFeaturesAddingCount:100];
}
- else if (buttonIndex == actionSheet.firstOtherButtonIndex + 5)
+ else if (buttonIndex == actionSheet.firstOtherButtonIndex + 4)
{
[self parseFeaturesAddingCount:1000];
}
- else if (buttonIndex == actionSheet.firstOtherButtonIndex + 6)
+ else if (buttonIndex == actionSheet.firstOtherButtonIndex + 5)
{
[self parseFeaturesAddingCount:10000];
}
- else if (buttonIndex == actionSheet.firstOtherButtonIndex + 7)
+ else if (buttonIndex == actionSheet.firstOtherButtonIndex + 6)
{
// PNW triangle
//
@@ -258,19 +255,19 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
free(polygonCoordinates);
}
}
- else if (buttonIndex == actionSheet.firstOtherButtonIndex + 8)
+ else if (buttonIndex == actionSheet.firstOtherButtonIndex + 7)
{
[self startWorldTour:actionSheet];
}
- else if (buttonIndex == actionSheet.firstOtherButtonIndex + 9)
+ else if (buttonIndex == actionSheet.firstOtherButtonIndex + 8)
{
[self presentAnnotationWithCustomCallout];
}
- else if (buttonIndex == actionSheet.firstOtherButtonIndex + 10)
+ else if (buttonIndex == actionSheet.firstOtherButtonIndex + 9)
{
[self.mapView removeAnnotations:self.mapView.annotations];
}
- else if (buttonIndex == actionSheet.firstOtherButtonIndex + 11)
+ else if (buttonIndex == actionSheet.firstOtherButtonIndex + 10)
{
if (_isShowingCustomStyleLayer)
{
@@ -281,6 +278,24 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
[self insertCustomStyleLayer];
}
}
+ else if (buttonIndex == actionSheet.firstOtherButtonIndex + 11)
+ {
+ NSString *fileContents = [NSString stringWithContentsOfFile:[self telemetryDebugLogfilePath] encoding:NSUTF8StringEncoding error:nil];
+ NSLog(@"%@", fileContents);
+ }
+ else if (buttonIndex == actionSheet.firstOtherButtonIndex + 12)
+ {
+ NSString *filePath = [self telemetryDebugLogfilePath];
+ if ([[NSFileManager defaultManager] isDeletableFileAtPath:filePath]) {
+ NSError *error;
+ BOOL success = [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
+ if (success) {
+ NSLog(@"Deleted telemetry log.");
+ } else {
+ NSLog(@"Error deleting telemetry log: %@", error.localizedDescription);
+ }
+ }
+ }
}
- (void)parseFeaturesAddingCount:(NSUInteger)featuresCount
@@ -481,6 +496,16 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
}];
}
+- (NSString *)telemetryDebugLogfilePath
+{
+ NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
+ [dateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
+ [dateFormatter setTimeZone:[NSTimeZone systemTimeZone]];
+ NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:[NSString stringWithFormat:@"telemetry_log-%@.json", [dateFormatter stringFromDate:[NSDate date]]]];
+
+ return filePath;
+}
+
#pragma mark - Destruction
- (void)dealloc
diff --git a/platform/ios/src/MGLMapboxEvents.h b/platform/ios/src/MGLMapboxEvents.h
index dba24885bf..6dbe93f64a 100644
--- a/platform/ios/src/MGLMapboxEvents.h
+++ b/platform/ios/src/MGLMapboxEvents.h
@@ -10,6 +10,7 @@ extern NSString *const MGLEventTypeMapTap;
extern NSString *const MGLEventTypeMapDragEnd;
extern NSString *const MGLEventTypeLocation;
extern NSString *const MGLEventTypeVisit;
+extern NSString *const MGLEventTypeLocalDebug;
extern NSString *const MGLEventKeyLatitude;
extern NSString *const MGLEventKeyLongitude;
@@ -24,6 +25,7 @@ extern NSString *const MGLEventKeyEmailEnabled;
extern NSString *const MGLEventKeyGestureID;
extern NSString *const MGLEventKeyArrivalDate;
extern NSString *const MGLEventKeyDepartureDate;
+extern NSString *const MGLEventKeyLocalDebugDescription;
extern NSString *const MGLEventGestureSingleTap;
extern NSString *const MGLEventGestureDoubleTap;
@@ -57,6 +59,10 @@ typedef NS_MUTABLE_DICTIONARY_OF(NSString *, id) MGLMutableMapboxEventAttributes
//
+ (void) pushEvent:(NSString *)event withAttributes:(MGLMapboxEventAttributes *)attributeDictionary;
++ (void) pushDebugEvent:(NSString *)event withAttributes:(MGLMapboxEventAttributes *)attributeDictionary;
+
++ (BOOL) debugLoggingEnabled;
+
// You can call these methods from any thread.
//
+ (BOOL) checkPushEnabled;
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