diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift index 0267ea4..ea40b42 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift @@ -43,6 +43,11 @@ open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol { /// Adds client parameters and makes calls performRequest() open func performRequestAddingClientParameters(with requestParameters: MVMCoreRequestParameters, model: ActionOpenPageModel, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws -> MVMCoreLoadRequestOperation? { + let actionUUID = MVMCoreActionHandler.getUUID(additionalData: additionalData) ?? UUID().uuidString + requestParameters.identifier = actionUUID + if !requestParameters.backgroundRequest, let pageType = requestParameters.pageType { + MVMCoreLoggingHandler.shared()?.logCoreEvent(.pageStarted(pageType: pageType, requestUUID: actionUUID)) + } // Adds any client parameters to the request parameters. if let parametersToFetch = model.clientParameters, let fetchedParameters = try await ClientParameterHandler().getClientParameters( @@ -53,7 +58,9 @@ open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol { requestParameters.add(fetchedParameters) } try Task.checkCancellation() - return MVMCoreLoadHandler.sharedGlobal()?.loadRequest(requestParameters, dataForPage: additionalData, delegateObject: delegateObject) + let coreLoadRequestOperation = MVMCoreLoadHandler.sharedGlobal()?.loadRequest(requestParameters, dataForPage: additionalData, delegateObject: delegateObject) + coreLoadRequestOperation?.identifier = actionUUID + return coreLoadRequestOperation } /// Ensures background requests do not have showing errors. diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m index d467e08..64d67ce 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m @@ -114,6 +114,7 @@ error.nativeDrivenErrorScreen = YES; error.silentError = NO; error.messageToDisplay = error.messageToDisplay ?: [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess]; + error.requestId = loadObject.identifier; //To track for errors due to invalid JSON or any errors thrown at viewcontroller level. } return error; } @@ -388,6 +389,7 @@ return [self loadBackgroundRequest:requestParameters dataForPage:dataForPage delegateObject:delegateObject]; } else { MVMCoreLoadRequestOperation *loadOperation = [[MVMCoreLoadRequestOperation alloc] initWithRequestParameters:requestParameters dataForPage:dataForPage delegateObject:delegateObject backgroundLoad:NO]; + loadOperation.identifier = requestParameters.identifier; [self.blockingLoadQueue addOperation:loadOperation]; return loadOperation; } @@ -395,6 +397,7 @@ - (MVMCoreLoadRequestOperation *)loadBackgroundRequest:(nonnull MVMCoreRequestParameters *)requestParameters dataForPage:(nullable NSDictionary *)dataForPage delegateObject:(nullable DelegateObject *)delegateObject { MVMCoreLoadRequestOperation *loadOperation = [[MVMCoreLoadRequestOperation alloc] initWithRequestParameters:requestParameters dataForPage:dataForPage delegateObject:delegateObject backgroundLoad:YES]; + loadOperation.identifier = requestParameters.identifier; [self.backgroundLoadQueue addOperation:loadOperation]; return loadOperation; } diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.h b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.h index 1ee321d..72875fa 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.h +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.h @@ -58,6 +58,9 @@ // The full response json @property (nullable, strong, nonatomic) NSDictionary *responseJSON; +//Unique Identifier for event tracking +@property (nullable, strong, nonatomic) NSString *identifier; + - (nullable instancetype)initWithPageJSON:(nullable NSDictionary *)pageJSON modulesJSON:(nullable NSDictionary *)modulesJSON requestParameters:(nullable MVMCoreRequestParameters *)requestParameters dataForPage:(nullable NSDictionary *)dataForPage delegateObject:(nullable DelegateObject *)delegateObject; - (nullable instancetype)initWithRequestParameters:(nullable MVMCoreRequestParameters *)requestParameters dataForPage:(nullable NSDictionary *)dataForPage delegateObject:(nullable DelegateObject *)delegateObject; diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.m index 25240c4..3be1428 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.m @@ -55,6 +55,9 @@ if (errorObject.messageToDisplay) { [responseInfo setObject:errorObject.messageToDisplay forKey:KeyUserMessage]; } + if (errorObject.requestId) { + self.identifier = errorObject.requestId; + } self.responseInfoMap = responseInfo; } return self; diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h index 0afd035..cbf775e 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h @@ -26,6 +26,7 @@ @property (nullable, nonatomic, readonly) NSString *finalLoadSource; @property (nonatomic) BOOL backgroundLoad; @property (nonatomic, getter=areDependenciesAdded) BOOL dependenciesAdded; +@property (nonnull, nonatomic, strong) NSString *identifier; /// Legacy flag for if this operation will have an alert to show when finished. @property (nonatomic, readonly) BOOL alertToShow; diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m index 1b1d184..5941bab 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m @@ -55,6 +55,7 @@ - (nullable instancetype)initWithLoadObject:(nullable MVMCoreLoadObject *)loadObject backgroundLoad:(BOOL)backgroundLoad { if (self = [self initWithRequestParameters:loadObject.requestParameters dataForPage:loadObject.dataForPage delegateObject:loadObject.delegateObject backgroundLoad:backgroundLoad]) { + loadObject.identifier = self.identifier; self.loadObject = loadObject; } return self; @@ -122,6 +123,7 @@ // No load requested, finish. MVMCoreLoadObject *loadObject = [[MVMCoreLoadObject alloc] initWithRequestParameters:nil dataForPage:self.dataForPage delegateObject:self.delegateObject]; + loadObject.identifier = self.identifier; loadObject.operation = self; [MVMCoreLoadRequestOperation loadFinished:loadObject loadedViewController:nil errorObject:nil]; } else if (self.loadObject) { @@ -153,13 +155,25 @@ MVMCoreRequestParameters *requestForMissingData = [MVMCoreLoadRequestOperation createRequestForDataWithLoadObject:loadObject]; if (!requestForMissingData) { + [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadCompleteFor:loadObject.requestParameters.pageType serverProcessingTime:@"0" requestURL:loadObject.requestParameters.URL.absoluteString requestUUID:loadObject.identifier isFromCache:loadObject.pageDataFromCache]; + // We have all the needed data, continue with the load. [MVMCoreLoadRequestOperation handleLoadObject:loadObject error:nil]; } else { + if(!self.backgroundLoad && loadObject.requestParameters.pageType) { + [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadStartedFor:loadObject.requestParameters.pageType requestUUID:loadObject.identifier requestURL:loadObject.requestParameters.URL.absoluteString]; + } + // Send a new request to the server. [MVMCoreLoadRequestOperation sendRequest:requestForMissingData loadObject:loadObject completionHandler:^(NSDictionary * _Nullable json) { - + + NSString *serverProcessTime = [(NSDictionary *)json objectChainOfKeysOrIndexes:@[@"ResponseInfo", @"timeStamp"]] ?: @"0"; + + if(!self.backgroundLoad && loadObject.requestParameters.pageType && serverProcessTime) { + [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadCompleteFor:loadObject.requestParameters.pageType serverProcessingTime:serverProcessTime requestURL:loadObject.requestParameters.URL.absoluteString requestUUID:loadObject.identifier isFromCache:loadObject.pageDataFromCache]; + } + // Process the data retrieved from the server. [MVMCoreLoadRequestOperation processJSONFromServer:json loadObject:loadObject completionHandler:^(MVMCoreLoadObject * _Nonnull loadObject, MVMCoreErrorObject * _Nullable error) { @@ -228,7 +242,7 @@ if (pageFromCache) { loadObject.pageType = self.requestParameters.pageType; } - + loadObject.identifier = self.identifier; // Store if we loaded from the cache or not. loadObject.pageDataFromCache = (pageFromCache != nil); loadObject.moduleDataFromCache = (modulesFromCache != nil); @@ -297,7 +311,6 @@ [MVMCoreLoadRequestOperation loadAbortedWithError:errorObject loadObject:loadObject]; } } else { - // Error with the request/response [[MVMCoreLoadHandler sharedGlobal] attachLoadInformation:loadObject toError:error]; [MVMCoreLoadRequestOperation loadAbortedWithError:error loadObject:loadObject]; diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreRequestParameters.h b/MVMCore/MVMCore/LoadHandling/MVMCoreRequestParameters.h index a2ca9bf..28d7f2d 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreRequestParameters.h +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreRequestParameters.h @@ -101,6 +101,9 @@ typedef NS_ENUM(NSInteger, MFLoadStyle) { /// A flag for if it should be a background request or not. @property (assign, nonatomic) BOOL backgroundRequest; +//Unique Identifier for event tracking +@property (nullable, strong, nonatomic) NSString *identifier; + // Creates an object with the given page type and extra parameters. Adds the extra parameters to the standard request parameters. Will add any modules needed by the page type by default. - (nullable instancetype)initWithPageType:(nonnull NSString *)pageType extraParameters:(nullable NSDictionary *)extraParameters; diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreEvent.swift b/MVMCore/MVMCore/OtherHandlers/MVMCoreEvent.swift index 73d8ecc..8ccae44 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreEvent.swift +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreEvent.swift @@ -82,9 +82,43 @@ public enum MVMCoreEvent { actionId: String ) + case pageStarted( + pageType: String, + requestUUID: String + ) + + case pageLoadStarted( + pageType: String, + requestUUID: String, + requestURL: String + ) + + case pageLoadComplete( + pageType: String, + requestUUID: String, + serverProcessingTime: String, + requestURL: String, + isFromCache: Bool + ) + + case pageProcessingComplete( + pageType: String, + requestUUID: String, + webUrl: String? + ) + + case pageRenderComplete( + pageType: String, + requestUUID: String, + templateName: String?, + controllerName: String, + error: String? + ) + public enum EventType: String { case action case clientParameter + case pageLoad public var notification: Notification.Name { return Notification.Name(rawValue: rawValue) @@ -102,6 +136,7 @@ public enum MVMCoreEvent { case .clientParameterStartFetch: return .clientParameter case .clientParameterTimeout: return .clientParameter case .clientParameterFetchComplete: return .clientParameter + case .pageStarted, .pageLoadStarted, .pageLoadComplete, .pageProcessingComplete, .pageRenderComplete: return .pageLoad } } @@ -118,10 +153,32 @@ public enum MVMCoreEvent { } } -extension MVMCoreLoggingHandler { +public extension MVMCoreLoggingHandler { func logCoreEvent(_ event: MVMCoreEvent, at timestamp: Int64 = Date.unixMillisecondsNow()) { recordEvent(event.type.rawValue, attributes: ["timestamp": timestamp, "event": event]) } - +} + +@objc public extension MVMCoreLoggingHandler { + + @objc func pageStarted(for pageType: String, requestUUID: String) { + logCoreEvent(.pageStarted(pageType: pageType, requestUUID: requestUUID)) + } + + @objc func logPageLoadStarted(for pageType: String, requestUUID: String, requestURL: String) { + logCoreEvent(.pageLoadStarted(pageType: pageType, requestUUID: requestUUID, requestURL: requestURL)) + } + + @objc func logPageLoadComplete(for pageType: String, serverProcessingTime: String, requestURL: String, requestUUID: String, isFromCache: Bool) { + logCoreEvent(.pageLoadComplete(pageType: pageType, requestUUID: requestUUID, serverProcessingTime: serverProcessingTime, requestURL: requestURL, isFromCache: isFromCache)) + } + + @objc func logPageProcessingComplete(for pageType: String, requestUUID: String, webUrl: String?) { + logCoreEvent(.pageProcessingComplete(pageType: pageType, requestUUID: requestUUID, webUrl: webUrl)) + } + + @objc func logPageRenderComplete(for pageType: String, requestUUID: String, templateName: String?, controllerName: String, error: String?) { + logCoreEvent(.pageRenderComplete(pageType: pageType, requestUUID: requestUUID, templateName: templateName, controllerName: controllerName, error: error)) + } }