diff --git a/Agent.xcodeproj/project.pbxproj b/Agent.xcodeproj/project.pbxproj index d76fdf9c..0f9a9b96 100644 --- a/Agent.xcodeproj/project.pbxproj +++ b/Agent.xcodeproj/project.pbxproj @@ -1500,6 +1500,9 @@ F8678AAE2CDBC62B008FD2A2 /* NRAutoCollectLogStressTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F8678AAC2CDBC62B008FD2A2 /* NRAutoCollectLogStressTest.m */; }; F8728E412ACC9D5A0056F641 /* NRMANetworkMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = F8728E402ACC9D5A0056F641 /* NRMANetworkMonitor.m */; }; F8728E422ACC9D5A0056F641 /* NRMANetworkMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = F8728E402ACC9D5A0056F641 /* NRMANetworkMonitor.m */; }; + F87925652D441AC300576F5D /* NRMAAttributeValidator.m in Sources */ = {isa = PBXBuildFile; fileRef = F87925642D441AC300576F5D /* NRMAAttributeValidator.m */; }; + F87925662D441AC300576F5D /* NRMAAttributeValidator.m in Sources */ = {isa = PBXBuildFile; fileRef = F87925642D441AC300576F5D /* NRMAAttributeValidator.m */; }; + F87925672D441AC300576F5D /* NRMAAttributeValidator.m in Sources */ = {isa = PBXBuildFile; fileRef = F87925642D441AC300576F5D /* NRMAAttributeValidator.m */; }; F87954D529E89D5F00319FCD /* NRMAWKWebViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0209ABB324E704A600E45C90 /* NRMAWKWebViewTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; F88C2CF72A2FA7AC00373EFE /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = F88C2CE32A2FA7AC00373EFE /* PrivacyInfo.xcprivacy */; }; F89167372BC9D1270085BCFC /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = F88C2CE32A2FA7AC00373EFE /* PrivacyInfo.xcprivacy */; }; @@ -2472,6 +2475,8 @@ F8678AAC2CDBC62B008FD2A2 /* NRAutoCollectLogStressTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NRAutoCollectLogStressTest.m; sourceTree = ""; }; F8728E402ACC9D5A0056F641 /* NRMANetworkMonitor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NRMANetworkMonitor.m; sourceTree = ""; }; F8728E562ACC9F840056F641 /* NRMANetworkMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NRMANetworkMonitor.h; sourceTree = ""; }; + F87925642D441AC300576F5D /* NRMAAttributeValidator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NRMAAttributeValidator.m; sourceTree = ""; }; + F879257B2D441ACC00576F5D /* NRMAAttributeValidator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NRMAAttributeValidator.h; sourceTree = ""; }; F88C2CE32A2FA7AC00373EFE /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; F89167382BC9D1540085BCFC /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "Platforms/WatchOS.platform/Developer/SDKs/WatchOS10.2.sdk/usr/lib/libc++.tbd"; sourceTree = DEVELOPER_DIR; }; F891A6BD2CB6F5C1007675F4 /* NRAutoLogCollector.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NRAutoLogCollector.m; sourceTree = ""; }; @@ -3202,9 +3207,7 @@ 34544D122A3142F900B5A8D1 /* NRMAEventManager.h */, 34544D132A3142F900B5A8D1 /* NRMAEventManager.m */, 34544D2D2A32489C00B5A8D1 /* NRMAAnalyticEventProtocol.h */, - 342C4B402A3BCE9300116992 /* AttributeValidatorProtocol.h */, - 342C4B562A3BD15A00116992 /* BlockAttributeValidator.h */, - 342C4B572A3BD15A00116992 /* BlockAttributeValidator.m */, + F879257C2D441DBF00576F5D /* AttributeValidator */, F8FBFB1E2A83CEBD00CDC8C5 /* Constants.m */, F8FBFB212A83CEDC00CDC8C5 /* Constants.h */, 340D64122AA92FCE00E63CC1 /* PersistentEventStore.h */, @@ -3885,6 +3888,18 @@ path = "Logging Stress Tests"; sourceTree = ""; }; + F879257C2D441DBF00576F5D /* AttributeValidator */ = { + isa = PBXGroup; + children = ( + 342C4B402A3BCE9300116992 /* AttributeValidatorProtocol.h */, + 342C4B562A3BD15A00116992 /* BlockAttributeValidator.h */, + 342C4B572A3BD15A00116992 /* BlockAttributeValidator.m */, + F879257B2D441ACC00576F5D /* NRMAAttributeValidator.h */, + F87925642D441AC300576F5D /* NRMAAttributeValidator.m */, + ); + path = AttributeValidator; + sourceTree = ""; + }; F8FBFA3C2A71A32400CDC8C5 /* Events */ = { isa = PBXGroup; children = ( @@ -5359,6 +5374,7 @@ 02FF49EF24DC645B00115469 /* NRMAAppInstallMetricGenerator.m in Sources */, 02FF4A7724DC64FC00115469 /* NRTimer.m in Sources */, 02FF484024DC614200115469 /* NRMALastActivityTraceController.m in Sources */, + F87925652D441AC300576F5D /* NRMAAttributeValidator.m in Sources */, 02FF489924DC61D200115469 /* NRMAURLSessionTaskDelegateBase.m in Sources */, 02FF4A9F24DC652E00115469 /* NRMAMemoryVitals.m in Sources */, 02FF4AB124DC652E00115469 /* NRMAJSON.m in Sources */, @@ -5591,6 +5607,7 @@ 02FF4C0224E3201400115469 /* NRMAHTTPTransactions.m in Sources */, 02FF4C0424E3201400115469 /* NRMAMachineMeasurementConsumer.m in Sources */, 2B33E5D32AA9160E00AEB7B4 /* NRMASessionEvent.m in Sources */, + F87925662D441AC300576F5D /* NRMAAttributeValidator.m in Sources */, F8FBFA632A78416300CDC8C5 /* NRMAMobileEvent.m in Sources */, 02FF4C0524E3201400115469 /* NRMAMetric.m in Sources */, F8E202DF2B07BA61008E0B7B /* NRMAOfflineStorage.m in Sources */, @@ -5870,6 +5887,7 @@ 348232F22BC5F20B0070FAC3 /* NRMAHarvesterConfiguration.m in Sources */, 348233122BC5F21B0070FAC3 /* NRMAExceptionHandlerJSONKeys.m in Sources */, 3482333A2BC5F2490070FAC3 /* NRMAClassNode.m in Sources */, + F87925672D441AC300576F5D /* NRMAAttributeValidator.m in Sources */, 3482334C2BC5F2710070FAC3 /* NRMAURLSessionTaskSearch.m in Sources */, 348232DA2BC5F2050070FAC3 /* NRMAHarvestableTrace.m in Sources */, 3482329B2BC5F1D70070FAC3 /* NRMAMeasurementException.m in Sources */, diff --git a/Agent/Analytics/AttributeValidatorProtocol.h b/Agent/Analytics/AttributeValidator/AttributeValidatorProtocol.h similarity index 100% rename from Agent/Analytics/AttributeValidatorProtocol.h rename to Agent/Analytics/AttributeValidator/AttributeValidatorProtocol.h diff --git a/Agent/Analytics/BlockAttributeValidator.h b/Agent/Analytics/AttributeValidator/BlockAttributeValidator.h similarity index 100% rename from Agent/Analytics/BlockAttributeValidator.h rename to Agent/Analytics/AttributeValidator/BlockAttributeValidator.h diff --git a/Agent/Analytics/BlockAttributeValidator.m b/Agent/Analytics/AttributeValidator/BlockAttributeValidator.m similarity index 93% rename from Agent/Analytics/BlockAttributeValidator.m rename to Agent/Analytics/AttributeValidator/BlockAttributeValidator.m index 9122f23f..edebc175 100644 --- a/Agent/Analytics/BlockAttributeValidator.m +++ b/Agent/Analytics/AttributeValidator/BlockAttributeValidator.m @@ -7,6 +7,9 @@ // #import "BlockAttributeValidator.h" +#import "Constants.h" +#import "NRMAAnalytics.h" +#import "NRLogger.h" @implementation BlockAttributeValidator diff --git a/Agent/Analytics/AttributeValidator/NRMAAttributeValidator.h b/Agent/Analytics/AttributeValidator/NRMAAttributeValidator.h new file mode 100644 index 00000000..f4d427f3 --- /dev/null +++ b/Agent/Analytics/AttributeValidator/NRMAAttributeValidator.h @@ -0,0 +1,14 @@ +// +// NRMAAttributeValidator.h +// Agent +// +// Created by Mike Bruin on 1/24/25. +// Copyright © 2025 New Relic. All rights reserved. +// + +#import +#import "AttributeValidatorProtocol.h" + +@interface NRMAAttributeValidator : NSObject + +@end diff --git a/Agent/Analytics/AttributeValidator/NRMAAttributeValidator.m b/Agent/Analytics/AttributeValidator/NRMAAttributeValidator.m new file mode 100644 index 00000000..1e88d00d --- /dev/null +++ b/Agent/Analytics/AttributeValidator/NRMAAttributeValidator.m @@ -0,0 +1,70 @@ +// +// NRMAAttributeValidator.m +// Agent +// +// Created by Mike Bruin on 1/24/25. +// Copyright © 2025 New Relic. All rights reserved. +// + +#import "NRMAAttributeValidator.h" +#import "Constants.h" +#import "NRMAAnalytics.h" +#import "NRLogger.h" + +@implementation NRMAAttributeValidator + +- (BOOL)eventTypeValidator:(NSString *)eventType { + return YES; +} + +- (BOOL)nameValidator:(NSString *)name { + if ([name length] == 0) { + NRLOG_AGENT_ERROR(@"invalid attribute: name length = 0"); + return false; + } + if ([name hasPrefix:@" "]) { + NRLOG_AGENT_ERROR(@"invalid attribute: name prefix = \" \""); + return false; + } + // check if attribute name is reserved or attribute name matches reserved prefix. + for (NSString* key in [NRMAAnalytics reservedKeywords]) { + if ([key isEqualToString:name]) { + NRLOG_AGENT_ERROR(@"invalid attribute: name prefix disallowed"); + return false; + } + } + for (NSString* key in [NRMAAnalytics reservedPrefixes]) { + if ([name hasPrefix:key]) { + NRLOG_AGENT_ERROR(@"invalid attribute: name prefix disallowed"); + return false; + } + } + + // check if attribute name exceeds max length. + if ([name length] > kNRMA_Attrib_Max_Name_Length) { + NRLOG_AGENT_ERROR(@"invalid attribute: name length exceeds limit"); + return false; + } + return true; +} + +- (BOOL)valueValidator:(id)value { + if ([value isKindOfClass:[NSString class]]) { + if ([(NSString*)value length] == 0) { + NRLOG_AGENT_ERROR(@"invalid attribute: value length = 0"); + return false; + } + else if ([(NSString*)value length] >= kNRMA_Attrib_Max_Value_Size_Bytes) { + NRLOG_AGENT_ERROR(@"invalid attribute: value exceeded maximum byte size exceeded"); + return false; + } + } + if (value == nil || [value isKindOfClass:[NSNull class]]) { + NRLOG_AGENT_ERROR(@"invalid attribute: value cannot be nil"); + return false; + } + + return true; +} + +@end diff --git a/Agent/Analytics/NRMAAnalytics.mm b/Agent/Analytics/NRMAAnalytics.mm index 93009570..16fa2c4f 100644 --- a/Agent/Analytics/NRMAAnalytics.mm +++ b/Agent/Analytics/NRMAAnalytics.mm @@ -29,7 +29,7 @@ #import "NRMAPayload.h" #import "NRMANetworkErrorEvent.h" #import "NRMASAM.h" -#import "BlockAttributeValidator.h" +#import "NRMAAttributeValidator.h" #import "NRMASessionEvent.h" //******************* THIS FILE HAS ARC DISABLED ******************* @@ -116,59 +116,9 @@ - (id) initWithSessionStartTimeMS:(long long) sessionStartTime { PersistentEventStore *eventStore = [[PersistentEventStore alloc] initWithFilename:filename andMinimumDelay:.025]; _eventManager = [[NRMAEventManager alloc] initWithPersistentStore:eventStore]; - _attributeValidator = [[BlockAttributeValidator alloc] initWithNameValidator:^BOOL(NSString *name) { - if ([name length] == 0) { - NRLOG_AGENT_ERROR(@"invalid attribute: name length = 0"); - return false; - } - if ([name hasPrefix:@" "]) { - NRLOG_AGENT_ERROR(@"invalid attribute: name prefix = \" \""); - return false; - } - // check if attribute name is reserved or attribute name matches reserved prefix. - for (NSString* key in [NRMAAnalytics reservedKeywords]) { - if ([key isEqualToString:name]) { - NRLOG_AGENT_ERROR(@"invalid attribute: name prefix disallowed"); - return false; - } - } - for (NSString* key in [NRMAAnalytics reservedPrefixes]) { - if ([name hasPrefix:key]) { - NRLOG_AGENT_ERROR(@"invalid attribute: name prefix disallowed"); - return false; - } - } - - // check if attribute name exceeds max length. - if ([name length] > kNRMA_Attrib_Max_Name_Length) { - NRLOG_AGENT_ERROR(@"invalid attribute: name length exceeds limit"); - return false; - } - return true; - - } valueValidator:^BOOL(id value) { - if ([value isKindOfClass:[NSString class]]) { - if ([(NSString*)value length] == 0) { - NRLOG_AGENT_ERROR(@"invalid attribute: value length = 0"); - return false; - } - else if ([(NSString*)value length] >= kNRMA_Attrib_Max_Value_Size_Bytes) { - NRLOG_AGENT_ERROR(@"invalid attribute: value exceeded maximum byte size exceeded"); - return false; - } - } - if (value == nil || [value isKindOfClass:[NSNull class]]) { - NRLOG_AGENT_ERROR(@"invalid attribute: value cannot be nil"); - return false; - } - - return true; - } andEventTypeValidator:^BOOL(NSString *eventType) { - return YES; - }]; + _attributeValidator = [[NRMAAttributeValidator alloc] init]; _sessionAttributeManager = [[NRMASAM alloc] initWithAttributeValidator:_attributeValidator]; - NSString* attributes = [self sessionAttributeJSONString]; if (attributes != nil && [attributes length] > 0) { NSDictionary* dictionary = [NSJSONSerialization JSONObjectWithData:[attributes dataUsingEncoding:NSUTF8StringEncoding] diff --git a/Agent/Analytics/NRMASAM.mm b/Agent/Analytics/NRMASAM.mm index 800c0101..801c2337 100644 --- a/Agent/Analytics/NRMASAM.mm +++ b/Agent/Analytics/NRMASAM.mm @@ -30,30 +30,31 @@ @implementation NRMASAM { - (id)initWithAttributeValidator:(__nullable id)validator { self = [super init]; if (self) { + attributeValidator = validator; + _attributePersistentStore = [[PersistentEventStore alloc] initWithFilename:[NRMASAM attributeFilePath] andMinimumDelay:.025]; _privateAttributePersistentStore = [[PersistentEventStore alloc] initWithFilename:[NRMASAM privateAttributeFilePath] andMinimumDelay:.025]; // Load public attributes from file. NSDictionary *lastSessionAttributes = [PersistentEventStore getLastSessionEventsFromFilename:[NRMASAM attributeFilePath]]; + attributeDict = [[NSMutableDictionary alloc] init]; if (lastSessionAttributes != nil) { - attributeDict = [lastSessionAttributes mutableCopy]; - } - if (!attributeDict) { - attributeDict = [[NSMutableDictionary alloc] init]; + for(NSString* key in [lastSessionAttributes allKeys]) { + [self setAttribute:key value:[lastSessionAttributes valueForKey:key]]; + } } // Load private attributes from file. NSDictionary *lastSessionPrivateAttributes = [PersistentEventStore getLastSessionEventsFromFilename:[NRMASAM privateAttributeFilePath]]; - + privateAttributeDict = [[NSMutableDictionary alloc] init]; + if (lastSessionPrivateAttributes != nil) { - privateAttributeDict = [lastSessionPrivateAttributes mutableCopy]; - } - if (!privateAttributeDict) { - privateAttributeDict = [[NSMutableDictionary alloc] init]; + for(NSString* key in [lastSessionPrivateAttributes allKeys]) { + [self setNRSessionAttribute:key value:[lastSessionPrivateAttributes valueForKey:key]]; + } } - - attributeValidator = validator; + } return self; } @@ -221,7 +222,7 @@ - (NSString*) sessionAttributeJSONString { + (NSString*) getLastSessionsAttributes { NSError *error; NSString *lastSessionAttributesJsonString = nil; - NSDictionary *lastSessionAttributes = [PersistentEventStore getLastSessionEventsFromFilename:[self attributeFilePath]]; + NSDictionary *lastSessionAttributes = [PersistentEventStore getLastSessionEventsFromFilename:[NRMASAM attributeFilePath]]; NSDictionary *lastSessionPrivateAttributes = [PersistentEventStore getLastSessionEventsFromFilename:[NRMASAM privateAttributeFilePath]]; NSMutableDictionary *mergedDictionary = [NSMutableDictionary dictionary]; diff --git a/Agent/General/NewRelicAgentInternal.m b/Agent/General/NewRelicAgentInternal.m index 9ea1d4b2..2ddd7213 100644 --- a/Agent/General/NewRelicAgentInternal.m +++ b/Agent/General/NewRelicAgentInternal.m @@ -54,6 +54,7 @@ #import "NRMAUDIDManager.h" #import "NRMASupportMetricHelper.h" #import "NRAutoLogCollector.h" +#import "NRMAAttributeValidator.h" // Support for teardown and re-setup of the agent within a process lifetime for our test harness @@ -568,7 +569,8 @@ - (void) onSessionStart { sessionStartTime:self.appSessionStartDate agentConfiguration:self.agentConfiguration platform:[NewRelicInternalUtils osName] - sessionId:[self currentSessionId]]; + sessionId:[self currentSessionId] + attributeValidator:[[NRMAAttributeValidator alloc] init]]; if (status != NotReachable) { // Because we support offline mode check if we're online before sending the handled exceptions [self.handledExceptionsController processAndPublishPersistedReports]; diff --git a/Agent/HandledException/NRMAHandledExceptions.h b/Agent/HandledException/NRMAHandledExceptions.h index 2578cf18..5892a319 100644 --- a/Agent/HandledException/NRMAHandledExceptions.h +++ b/Agent/HandledException/NRMAHandledExceptions.h @@ -24,7 +24,8 @@ extern const NSString* kHexBackupStoreFolder; sessionStartTime:(NSDate*)sessionStartDate agentConfiguration:(NRMAAgentConfiguration*)agentConfiguration platform:(NSString*)platform - sessionId:(NSString*)sessionId; + sessionId:(NSString*)sessionId + attributeValidator:(id) attributeValidator; - (void) recordHandledException:(NSException*) exception attributes:(NSDictionary* _Nullable)attributes; diff --git a/Agent/HandledException/NRMAHandledExceptions.mm b/Agent/HandledException/NRMAHandledExceptions.mm index 29953b4e..5300824d 100644 --- a/Agent/HandledException/NRMAHandledExceptions.mm +++ b/Agent/HandledException/NRMAHandledExceptions.mm @@ -22,6 +22,7 @@ #import "NRMABool.h" #import "NRMASupportMetricHelper.h" #import "Constants.h" +#import "NRMAAttributeValidator.h" @interface NRMAAnalytics(Protected) // Because the NRMAAnalytics class interfaces with non Objective-C++ files, we cannot expose the API on the header. Therefore, we must use this reference. @@ -39,6 +40,7 @@ @implementation NRMAHandledExceptions { NewRelic::Hex::HexUploadPublisher* _publisher; NRMAAnalytics *analyticsParent; + id _attributeValidator; } - (void) dealloc { @@ -49,6 +51,7 @@ - (void) dealloc { self.sessionId = nil; self.sessionStartDate = nil; + [_attributeValidator release]; [super dealloc]; } @@ -57,7 +60,8 @@ - (instancetype) initWithAnalyticsController:(NRMAAnalytics*)analytics sessionStartTime:(NSDate*)sessionStartDate agentConfiguration:(NRMAAgentConfiguration*)agentConfiguration platform:(NSString*)platform - sessionId:(NSString*)sessionId { + sessionId:(NSString*)sessionId + attributeValidator:(id) attributeValidator { if (analytics == nil || sessionStartDate == nil || [agentConfiguration applicationToken] == nil || platform == nil || sessionId == nil) { NSMutableArray* missingParams = [[NSMutableArray new] autorelease]; if ([agentConfiguration applicationToken] == nil) [missingParams addObject:@"appToken"]; @@ -71,6 +75,8 @@ - (instancetype) initWithAnalyticsController:(NRMAAnalytics*)analytics self = [super init]; if (self) { analyticsParent = analytics; + + _attributeValidator = [attributeValidator retain]; _analytics = std::shared_ptr([analytics analyticsController]); self.sessionStartDate = sessionStartDate; @@ -198,7 +204,8 @@ - (void) recordError:(NSError * _Nonnull)error resultMap, [self createThreadVector:callstack length:frames] ); - NRMAExceptionReportAdaptor* contextAdapter = [[[NRMAExceptionReportAdaptor alloc] initWithReport:report attributeValidator:[analyticsParent getAttributeValidator]] autorelease]; + + NRMAExceptionReportAdaptor* contextAdapter = [[[NRMAExceptionReportAdaptor alloc] initWithReport:report attributeValidator:_attributeValidator] autorelease]; if (attributes != nil) { [contextAdapter addAttributesNewValidation:attributes]; @@ -218,7 +225,7 @@ - (void) recordError:(NSError * _Nonnull)error [self createThreadVector:callstack length:frames] ); - NRMAExceptionReportAdaptor* contextAdapter = [[[NRMAExceptionReportAdaptor alloc] initWithReport:report attributeValidator:[analyticsParent getAttributeValidator]] autorelease]; + NRMAExceptionReportAdaptor* contextAdapter = [[[NRMAExceptionReportAdaptor alloc] initWithReport:report attributeValidator:_attributeValidator] autorelease]; if (attributes != nil) { [contextAdapter addAttributes:attributes]; @@ -268,7 +275,7 @@ - (void) recordHandledException:(NSException*)exception [self checkOffline:report]; - NRMAExceptionReportAdaptor* contextAdapter = [[[NRMAExceptionReportAdaptor alloc] initWithReport:report attributeValidator:[analyticsParent getAttributeValidator]] autorelease]; + NRMAExceptionReportAdaptor* contextAdapter = [[[NRMAExceptionReportAdaptor alloc] initWithReport:report attributeValidator:_attributeValidator] autorelease]; if (attributes != nil) { [contextAdapter addAttributesNewValidation:attributes]; @@ -287,7 +294,7 @@ - (void) recordHandledException:(NSException*)exception [self checkOffline:report]; - NRMAExceptionReportAdaptor* contextAdapter = [[[NRMAExceptionReportAdaptor alloc] initWithReport:report attributeValidator:[analyticsParent getAttributeValidator]] autorelease]; + NRMAExceptionReportAdaptor* contextAdapter = [[[NRMAExceptionReportAdaptor alloc] initWithReport:report attributeValidator:_attributeValidator] autorelease]; if (attributes != nil) { [contextAdapter addAttributes:attributes]; @@ -368,7 +375,7 @@ - (void) recordHandledExceptionWithStackTrace:(NSDictionary*)exceptionDictionary report->setAttributeNoValidation("timeSinceLoad", [[[NSDate new] autorelease] timeIntervalSinceDate:self.sessionStartDate]); [self checkOffline:report]; - NRMAExceptionReportAdaptor* contextAdapter = [[[NRMAExceptionReportAdaptor alloc] initWithReport:report attributeValidator:[analyticsParent getAttributeValidator]] autorelease]; + NRMAExceptionReportAdaptor* contextAdapter = [[[NRMAExceptionReportAdaptor alloc] initWithReport:report attributeValidator:_attributeValidator] autorelease]; if (exceptionDictionary != nil) { [contextAdapter addAttributesNewValidation:exceptionDictionary]; @@ -385,7 +392,7 @@ - (void) recordHandledExceptionWithStackTrace:(NSDictionary*)exceptionDictionary report->setAttribute("timeSinceLoad", [[[NSDate new] autorelease] timeIntervalSinceDate:self.sessionStartDate]); [self checkOffline:report]; - NRMAExceptionReportAdaptor* contextAdapter = [[[NRMAExceptionReportAdaptor alloc] initWithReport:report attributeValidator:[analyticsParent getAttributeValidator]] autorelease]; + NRMAExceptionReportAdaptor* contextAdapter = [[[NRMAExceptionReportAdaptor alloc] initWithReport:report attributeValidator:_attributeValidator] autorelease]; if (exceptionDictionary != nil) { [contextAdapter addAttributes:exceptionDictionary]; diff --git a/Agent/Utilities/NRLogger.m b/Agent/Utilities/NRLogger.m index c1ad8c5e..43ed95e0 100644 --- a/Agent/Utilities/NRLogger.m +++ b/Agent/Utilities/NRLogger.m @@ -330,6 +330,24 @@ - (NSMutableDictionary*) commonBlockDict { if (nativePlatform) [commonAttributes setObject:nativePlatform forKey:NRLogMessageInstrumentationCollectorKey]; if (nrAppId) [commonAttributes setObject:nrAppId forKey:NRLogMessageAppIdKey]; + + NSString* sessionAttributes = [[NewRelicAgentInternal sharedInstance].analyticsController sessionAttributeJSONString]; + if (sessionAttributes != nil && [sessionAttributes length] > 0) { + NSDictionary* dictionary = [NSJSONSerialization JSONObjectWithData:[sessionAttributes dataUsingEncoding:NSUTF8StringEncoding] + options:0 + error:nil]; + for (NSString *key in dictionary) { + id value = [dictionary objectForKey:key]; + + + if (value) { + [commonAttributes setObject:value forKey:key]; + } + } + } + + + return commonAttributes; } @@ -598,7 +616,6 @@ - (void) processNextUploadTask { NSData *formattedData = [self->uploadQueue firstObject]; if (self->debugLogs) { - //NSString* logMessagesJson = [NSString stringWithFormat:@"[ %@ ]", [[NSString alloc] initWithData:formattedData encoding:NSUTF8StringEncoding]]; NSArray* decode = [NSJSONSerialization JSONObjectWithData:formattedData options:0 error:nil]; @@ -609,9 +626,6 @@ - (void) processNextUploadTask { NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString: self->logURL]]; [req setValue:self->logIngestKey forHTTPHeaderField:@"X-App-License-Key"]; [req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - - //NSString* contentEncoding = message.length <= 512 ? kNRMAIdentityHeader : kNRMAGZipHeader; - [req setValue:kNRMAGZipHeader forHTTPHeaderField:kNRMAContentEncodingHeader]; req.HTTPMethod = @"POST"; diff --git a/Tests/Stress-Tests/Handled Exception Tests/NRMAHandledExcaptionStressor.mm b/Tests/Stress-Tests/Handled Exception Tests/NRMAHandledExcaptionStressor.mm index 210d1db6..b6c2d871 100644 --- a/Tests/Stress-Tests/Handled Exception Tests/NRMAHandledExcaptionStressor.mm +++ b/Tests/Stress-Tests/Handled Exception Tests/NRMAHandledExcaptionStressor.mm @@ -45,7 +45,8 @@ - (void)setUp { sessionStartTime:[NSDate date] agentConfiguration:_agentConfiguration platform:@"iOS" - sessionId:@"id"]; + sessionId:@"id" + attributeValidator:[[NRMAAttributeValidator alloc] init]]; NSUInteger procCount = [[NSProcessInfo processInfo] processorCount]; self.semaphore = dispatch_semaphore_create(procCount); diff --git a/Tests/Unit-Tests/NewRelicAgentTests/Analytics-Tests/NRMASAMTest.mm b/Tests/Unit-Tests/NewRelicAgentTests/Analytics-Tests/NRMASAMTest.mm index fd5707b2..437e7707 100644 --- a/Tests/Unit-Tests/NewRelicAgentTests/Analytics-Tests/NRMASAMTest.mm +++ b/Tests/Unit-Tests/NewRelicAgentTests/Analytics-Tests/NRMASAMTest.mm @@ -24,8 +24,21 @@ @implementation NRMASAMTest - (void)setUp { [super setUp]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + if([fileManager fileExistsAtPath:[NSString stringWithFormat:@"%@/%@",[NewRelicInternalUtils getStorePath],kNRMA_Attrib_file]]) { + [fileManager removeItemAtPath:[NSString stringWithFormat:@"%@/%@",[NewRelicInternalUtils getStorePath],kNRMA_Attrib_file] error:nil]; + } + if([fileManager fileExistsAtPath:[NSString stringWithFormat:@"%@/%@",[NewRelicInternalUtils getStorePath],kNRMA_Attrib_file_private]]) { + [fileManager removeItemAtPath:[NSString stringWithFormat:@"%@/%@",[NewRelicInternalUtils getStorePath],kNRMA_Attrib_file_private] error:nil]; + } + + // [NRLogger setLogLevels:NRLogLevelDebug]; manager = [self samTest]; +} + +- (void) tearDown { + [super tearDown]; [manager removeAllSessionAttributes]; } @@ -129,6 +142,7 @@ - (void) testIncrementSessionAttribute { error:nil]; XCTAssertTrue([decode[attribute] isEqual:@(2)]); } + - (void) testIncrementSessionAttributeDiffTypes { NSString* attribute = @"incrementableAttribute"; float initialValue = 1.2; @@ -266,8 +280,68 @@ - (void) testClearLastSessionsAnalytics { } +- (void)waitForAttributesToPersist:(NSArray *)expectedAttributes timeout:(NSTimeInterval)timeout { + NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeout]; + while ([timeoutDate timeIntervalSinceNow] > 0) { + NSString *attributes = [NRMASAM getLastSessionsAttributes]; + NSDictionary *decode = [NSJSONSerialization JSONObjectWithData:[attributes dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil]; + + BOOL allAttributesPersisted = YES; + for (NSString *attribute in expectedAttributes) { + if (![decode objectForKey:attribute]) { + allAttributesPersisted = NO; + break; + } + } + + if (allAttributesPersisted) { + return; + } + + // Wait a short period before retrying + [NSThread sleepForTimeInterval:0.1]; + } + XCTFail(@"Failed to persist expected attributes."); +} + +- (void)testPersistedSessionAnalytics { + + NSString *attribute = @"blarg"; + NSString *attribute2 = @"blarg2"; + NSString *attribute3 = @"privateBlarg3"; + NSString *attribute4 = @"Blarg4"; + NSString *attribute5 = @"privateBlarg5"; + + // Set attributes + XCTAssertTrue([manager setSessionAttribute:attribute value:@"blurg"], @"Failed to successfully set session attribute"); + XCTAssertTrue([manager setSessionAttribute:attribute2 value:@"blurg2"], @"Failed to successfully set session attribute"); + XCTAssertTrue([manager setNRSessionAttribute:attribute3 value:@"blurg2"], @"Failed to successfully set private session attribute"); + + NSString *attributes = [manager sessionAttributeJSONString]; + NSDictionary *decode = [NSJSONSerialization JSONObjectWithData:[attributes dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil]; + + XCTAssertEqual(decode.count, 3); + + manager = nil; + // Wait for persistence + [self waitForAttributesToPersist:@[attribute, attribute2, attribute3] timeout:10]; + + manager = [self samTest]; + + XCTAssertTrue([manager setSessionAttribute:attribute4 value:@"blurg"], @"Failed to successfully set session attribute"); + XCTAssertTrue([manager setNRSessionAttribute:attribute5 value:@"blurg2"], @"Failed to successfully set private session attribute"); + + attributes = [manager sessionAttributeJSONString]; + decode = [NSJSONSerialization JSONObjectWithData:[attributes dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil]; + + XCTAssertTrue([[decode allKeys] containsObject:attribute], @"Should have persisted and new attribute 1."); + XCTAssertTrue([[decode allKeys] containsObject:attribute2], @"Should have persisted and new attribute 2."); + XCTAssertTrue([[decode allKeys] containsObject:attribute3], @"Should have persisted and new private attribute 3."); + XCTAssertTrue([[decode allKeys] containsObject:attribute4], @"Should have persisted and new attribute 4."); + XCTAssertTrue([[decode allKeys] containsObject:attribute5], @"Should have persisted and new private attribute 5."); +} + - (void) testClearPersistedSessionAnalytics { - NRMASAM *manager = [self samTest]; NSString *attribute = @"blarg"; NSString *attribute2 = @"blarg2"; diff --git a/Tests/Unit-Tests/NewRelicAgentTests/Handled-Exception-Tests/TestHandledExceptionController.mm b/Tests/Unit-Tests/NewRelicAgentTests/Handled-Exception-Tests/TestHandledExceptionController.mm index fe5c184b..5db697b6 100644 --- a/Tests/Unit-Tests/NewRelicAgentTests/Handled-Exception-Tests/TestHandledExceptionController.mm +++ b/Tests/Unit-Tests/NewRelicAgentTests/Handled-Exception-Tests/TestHandledExceptionController.mm @@ -13,6 +13,7 @@ #import "NRMAAgentConfiguration.h" #import "NewRelicInternalUtils.h" +#import "NRMAAttributeValidator.h" #import #import "NRAgentTestBase.h" @@ -63,14 +64,16 @@ - (void) testBadParams { sessionStartTime:0 agentConfiguration:nil platform:nil - sessionId:nil]); + sessionId:nil + attributeValidator:nil]); NRMAAnalytics* analytics = [[NRMAAnalytics alloc] initWithSessionStartTimeMS:0]; NRMAHandledExceptions* exceptions = [[NRMAHandledExceptions alloc] initWithAnalyticsController:nil sessionStartTime:0 agentConfiguration:nil platform:nil - sessionId:nil]; + sessionId:nil + attributeValidator:nil]; XCTAssertTrue(exceptions == nil); @@ -81,7 +84,8 @@ - (void) testBadParams { sessionStartTime:0 agentConfiguration:nil platform:nil - sessionId:nil]; + sessionId:nil + attributeValidator:nil]; XCTAssertTrue(exceptions == nil); @@ -94,7 +98,8 @@ - (void) testBadParams { sessionStartTime:0 agentConfiguration:agentConfig platform:nil - sessionId:nil]; + sessionId:nil + attributeValidator:nil]; XCTAssertTrue(exceptions == nil); @@ -104,7 +109,8 @@ - (void) testBadParams { sessionStartTime:0 agentConfiguration:agentConfig platform:@"iOS" - sessionId:nil]; + sessionId:nil + attributeValidator:nil]; XCTAssertTrue(exceptions == nil); @@ -119,14 +125,16 @@ - (void) testBadParamsNewEventSystem { sessionStartTime:0 agentConfiguration:nil platform:nil - sessionId:nil]); + sessionId:nil + attributeValidator:nil]); NRMAAnalytics* analytics = [[NRMAAnalytics alloc] initWithSessionStartTimeMS:0]; NRMAHandledExceptions* exceptions = [[NRMAHandledExceptions alloc] initWithAnalyticsController:nil sessionStartTime:0 agentConfiguration:nil platform:nil - sessionId:nil]; + sessionId:nil + attributeValidator:nil]; XCTAssertTrue(exceptions == nil); @@ -137,7 +145,8 @@ - (void) testBadParamsNewEventSystem { sessionStartTime:0 agentConfiguration:nil platform:nil - sessionId:nil]; + sessionId:nil + attributeValidator:nil]; XCTAssertTrue(exceptions == nil); @@ -150,7 +159,8 @@ - (void) testBadParamsNewEventSystem { sessionStartTime:0 agentConfiguration:agentConfig platform:nil - sessionId:nil]; + sessionId:nil + attributeValidator:nil]; XCTAssertTrue(exceptions == nil); @@ -160,7 +170,8 @@ - (void) testBadParamsNewEventSystem { sessionStartTime:0 agentConfiguration:agentConfig platform:@"iOS" - sessionId:nil]; + sessionId:nil + attributeValidator:nil]; XCTAssertTrue(exceptions == nil); @@ -180,7 +191,8 @@ - (void) testHandleException { sessionStartTime:[NSDate new] agentConfiguration:agentConfig platform:[NewRelicInternalUtils osName] - sessionId:@"sessionId"]; + sessionId:@"sessionId" + attributeValidator:[[NRMAAttributeValidator alloc] init]]; XCTAssertNoThrow([hexController recordHandledException:[NSException exceptionWithName:@"Hot Tea Exception" reason:@"the Tea is too hot" @@ -211,7 +223,8 @@ - (void) testHandleExceptionWithStackTrace { sessionStartTime:[NSDate new] agentConfiguration:agentConfig platform:[NewRelicInternalUtils osName] - sessionId:@"sessionId"]; + sessionId:@"sessionId" + attributeValidator:[[NRMAAttributeValidator alloc] init]]; id dict = @{@"name": @"Exception name not found", @"reason": @"Reason not found", @@ -238,7 +251,8 @@ - (void) testHandleExceptionWithStackTraceNewEventSystem { sessionStartTime:[NSDate new] agentConfiguration:agentConfig platform:[NewRelicInternalUtils osName] - sessionId:@"sessionId"]; + sessionId:@"sessionId" + attributeValidator:[[NRMAAttributeValidator alloc] init]]; id dict = @{@"name": @"Exception name not found", @"reason": @"Reason not found", @@ -266,7 +280,8 @@ - (void) testPlatform { sessionStartTime:[NSDate new] agentConfiguration:agentConfig platform:[NewRelicInternalUtils osName] - sessionId:@"sessionId"]; + sessionId:@"sessionId" + attributeValidator:[[NRMAAttributeValidator alloc] init]]; XCTAssertTrue([hexController fbsPlatformFromString:@"iOS"] == com::newrelic::mobile::fbs::Platform_iOS, @"Method returned %d, but should be %d", [hexController fbsPlatformFromString:@"iOS"],com::newrelic::mobile::fbs::Platform_iOS ); XCTAssertTrue([hexController fbsPlatformFromString:@"tvOS"] == com::newrelic::mobile::fbs::Platform_tvOS,@"Method returned %d, but should be %d", [hexController fbsPlatformFromString:@"tvOS"],com::newrelic::mobile::fbs::Platform_tvOS); @@ -284,7 +299,8 @@ - (void) testDontRecordUnThrownExceptions { sessionStartTime:[NSDate new] agentConfiguration:agentConfig platform:[NewRelicInternalUtils osName] - sessionId:@"sessionId"]; + sessionId:@"sessionId" + attributeValidator:[[NRMAAttributeValidator alloc] init]]; id mockLogger = [OCMockObject mockForClass:[NRLogger class]]; @@ -319,7 +335,8 @@ - (void) testRecordError { sessionStartTime:[NSDate new] agentConfiguration:agentConfig platform:[NewRelicInternalUtils osName] - sessionId:@"sessionId"]; + sessionId:@"sessionId" + attributeValidator:[[NRMAAttributeValidator alloc] init]]; NSError* error = [NSError errorWithDomain:@"" code:NSURLErrorUnknown userInfo:@{}]; @@ -351,7 +368,8 @@ - (void) testRecordErrorNewEventSystem { sessionStartTime:[NSDate new] agentConfiguration:agentConfig platform:[NewRelicInternalUtils osName] - sessionId:@"sessionId"]; + sessionId:@"sessionId" + attributeValidator:[[NRMAAttributeValidator alloc] init]]; NSError* error = [NSError errorWithDomain:@"" code:NSURLErrorUnknown userInfo:@{}]; diff --git a/Tests/Unit-Tests/NewRelicAgentTests/Uncategorized/NRLoggerTests.h b/Tests/Unit-Tests/NewRelicAgentTests/Uncategorized/NRLoggerTests.h index 71f918bf..6150a793 100644 --- a/Tests/Unit-Tests/NewRelicAgentTests/Uncategorized/NRLoggerTests.h +++ b/Tests/Unit-Tests/NewRelicAgentTests/Uncategorized/NRLoggerTests.h @@ -18,4 +18,7 @@ } @property (nonatomic) int fileDescriptor; @property (nonatomic, strong) dispatch_source_t source; + +@property id mockNewRelicInternals; + @end diff --git a/Tests/Unit-Tests/NewRelicAgentTests/Uncategorized/NRLoggerTests.m b/Tests/Unit-Tests/NewRelicAgentTests/Uncategorized/NRLoggerTests.m index da02f03c..f89990c1 100644 --- a/Tests/Unit-Tests/NewRelicAgentTests/Uncategorized/NRLoggerTests.m +++ b/Tests/Unit-Tests/NewRelicAgentTests/Uncategorized/NRLoggerTests.m @@ -24,12 +24,16 @@ #import "NRTestConstants.h" #import "NRAutoLogCollector.h" #import +#import "NewRelicAgentInternal.h" +#import @interface NRLogger() + (NRLogger *)logger; - (NSMutableDictionary*) commonBlockDict; @end +static NewRelicAgentInternal* _sharedInstance; + @implementation NRLoggerTests - (void) setUp { @@ -39,9 +43,16 @@ - (void) setUp [NRLogger setLogLevels:NRLogLevelDebug]; [NRLogger setRemoteLogLevel:NRLogLevelDebug]; - [NRLogger setLogEntityGuid:@"Entity-Guid-XXXX"]; + + self.mockNewRelicInternals = [OCMockObject mockForClass:[NewRelicAgentInternal class]]; + _sharedInstance = [[NewRelicAgentInternal alloc] init]; + _sharedInstance.analyticsController = [[NRMAAnalytics alloc] initWithSessionStartTimeMS:0.0]; + [[[[self.mockNewRelicInternals stub] classMethod] andReturn:_sharedInstance] sharedInstance]; + + [_sharedInstance.analyticsController setSessionAttribute:@"myAttribute" value:@(1)]; + NRMAAgentConfiguration *config = [[NRMAAgentConfiguration alloc] initWithAppToken:[[NRMAAppToken alloc] initWithApplicationToken:kNRMA_ENABLED_STAGING_APP_TOKEN] collectorAddress:KNRMA_TEST_COLLECTOR_HOST crashAddress:nil]; @@ -86,7 +97,8 @@ - (void) tearDown [NRMAMeasurements removeMeasurementConsumer:helper]; helper = nil; - + [self.mockNewRelicInternals stopMocking]; + _sharedInstance = nil; [NRMAMeasurements shutdown]; [NRMAFlags disableFeatures: NRFeatureFlag_LogReporting]; [NRLogger setLogTargets:NRLogTargetConsole]; @@ -183,6 +195,10 @@ - (void) testNRLogger { XCTAssertTrue([[decodedCommonBlock objectForKey:NRLogMessageInstrumentationProviderKey] isEqualToString:NRLogMessageMobileValue],@"instrumentation provider set incorrectly"); XCTAssertTrue([[decodedCommonBlock objectForKey:NRLogMessageInstrumentationVersionKey] isEqualToString:@"DEV"],@"instrumentation name set incorrectly"); + // Check for added session attributes + XCTAssertTrue([[decodedCommonBlock objectForKey:@"myAttribute"] isEqualToNumber:@(1)],@"session attribute set incorrectly"); + + #if TARGET_OS_WATCH XCTAssertTrue([[decodedCommonBlock objectForKey:NRLogMessageInstrumentationNameKey] isEqualToString:@"watchOSAgent"],@"instrumentation name set incorrectly"); #else