diff --git a/src/Components/DynamicFields.res b/src/Components/DynamicFields.res index 359062302..f089665ba 100644 --- a/src/Components/DynamicFields.res +++ b/src/Components/DynamicFields.res @@ -287,44 +287,6 @@ let make = ( (dynamicFieldsToRenderInsideBilling->Js.Array2.length > 1 || !isOnlyInfoElementPresent) }, (dynamicFieldsToRenderInsideBilling, isOnlyInfoElementPresent)) - React.useEffect1(() => { - let fieldsArrStr = fieldsArr->Js.Array2.map(field => { - field->PaymentMethodsRecord.paymentMethodFieldToStrMapper->Js.Json.string - }) - let dynamicFieldsToRenderOutsideBillingStr = - dynamicFieldsToRenderOutsideBilling->Js.Array2.map(field => { - field->PaymentMethodsRecord.paymentMethodFieldToStrMapper->Js.Json.string - }) - let dynamicFieldsToRenderInsideBillingStr = - dynamicFieldsToRenderInsideBilling->Js.Array2.map(field => { - field->PaymentMethodsRecord.paymentMethodFieldToStrMapper->Js.Json.string - }) - let requiredFieldsStr = requiredFields->Js.Array2.map(field => { - field.required_field->Js.Json.string - }) - - let loggerPayload = - [ - ("requiredFields", requiredFieldsStr->Js.Json.array), - ("fieldsArr", fieldsArrStr->Js.Json.array), - ( - "dynamicFieldsToRenderOutsideBilling", - dynamicFieldsToRenderOutsideBillingStr->Js.Json.array, - ), - ( - "dynamicFieldsToRenderInsideBilling", - dynamicFieldsToRenderInsideBillingStr->Js.Json.array, - ), - ("isRenderDynamicFieldsInsideBilling", isRenderDynamicFieldsInsideBilling->Js.Json.boolean), - ("isOnlyInfoElementPresent", isOnlyInfoElementPresent->Js.Json.boolean), - ("isInfoElementPresent", isInfoElementPresent->Js.Json.boolean), - ] - ->Js.Dict.fromArray - ->Js.Json.object_ - logger.setLogInfo(~value=loggerPayload->Js.Json.stringify, ~eventName=DYNAMIC_FIELDS_RENDER, ()) - None - }, fieldsArr) - { fieldsArr->Js.Array2.length > 0 ? <> diff --git a/src/GlobalVars.res b/src/GlobalVars.res index 6320c4e18..0580a11fb 100644 --- a/src/GlobalVars.res +++ b/src/GlobalVars.res @@ -9,6 +9,7 @@ @val external sentryScriptUrl: string = "sentryScriptUrl" @val external enableLogging: bool = "enableLogging" @val external loggingLevelStr: string = "loggingLevel" +@val external maxLogsPushedPerEventName: int = "maxLogsPushedPerEventName" let targetOrigin: string = "*" let isInteg = sdkUrl === "https://dev.hyperswitch.io" let isSandbox = sdkUrl === "https://beta.hyperswitch.io" diff --git a/src/LoaderController.res b/src/LoaderController.res index c33ec17e8..a83b8180c 100644 --- a/src/LoaderController.res +++ b/src/LoaderController.res @@ -254,7 +254,7 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger) => { } } - logger.setLogInfo(~value="paymentElementCreate", ~eventName=APP_RENDERED, ()) + logger.setLogInfo(~value=Window.href, ~eventName=APP_RENDERED, ()) [ ("iframeId", "no-element"->Js.Json.string), ("publishableKey", ""->Js.Json.string), diff --git a/src/Window.res b/src/Window.res index 805d63fba..fd57be2cc 100644 --- a/src/Window.res +++ b/src/Window.res @@ -113,7 +113,11 @@ external userAgent: string = "userAgent" @val @scope("navigator") external sendBeacon: (string, string) => unit = "sendBeacon" -@val @scope(("window", "location")) external hostname: string = "hostname" +@val @scope(("window", "location")) +external hostname: string = "hostname" + +@val @scope(("window", "location")) +external href: string = "href" let isSandbox = hostname === "beta.hyperswitch.io" diff --git a/src/orca-loader/Hyper.res b/src/orca-loader/Hyper.res index 9a5688375..f72762784 100644 --- a/src/orca-loader/Hyper.res +++ b/src/orca-loader/Hyper.res @@ -113,12 +113,7 @@ let make = (publishableKey, options: option, analyticsInfo: option { logger.setMerchantId(publishableKey) logger.setSessionId(sessionID) - logger.setLogInfo( - ~value="orca-sdk initiated", - ~eventName=APP_INITIATED, - ~timestamp=sdkTimestamp, - (), - ) + logger.setLogInfo(~value=Window.href, ~eventName=APP_INITIATED, ~timestamp=sdkTimestamp, ()) } }->Sentry.sentryLogger switch Window.getHyper->Js.Nullable.toOption { @@ -326,7 +321,7 @@ let make = (publishableKey, options: option, analyticsInfo: option { - logger.setLogInfo(~value="orca.elements called", ~eventName=ORCA_ELEMENTS_CALLED, ()) + open Promise let clientSecretId = elementsOptions ->Js.Json.decodeObject @@ -334,8 +329,15 @@ let make = (publishableKey, options: option, analyticsInfo: optionBelt.Option.flatMap(Js.Json.decodeString) ->Belt.Option.getWithDefault("") clientSecret := clientSecretId - - logger.setClientSecret(clientSecretId) + Js.Promise.make((~resolve, ~reject as _) => { + logger.setClientSecret(clientSecretId) + resolve(. Js.Json.null) + }) + ->then(_ => { + logger.setLogInfo(~value=Window.href, ~eventName=ORCA_ELEMENTS_CALLED, ()) + resolve() + }) + ->ignore Elements.make( elementsOptions, diff --git a/src/orca-log-catcher/ErrorBoundary.res b/src/orca-log-catcher/ErrorBoundary.res index f8aa14567..a636fab58 100644 --- a/src/orca-log-catcher/ErrorBoundary.res +++ b/src/orca-log-catcher/ErrorBoundary.res @@ -98,11 +98,7 @@ module ErrorCard = { let make = (~error: Sentry.ErrorBoundary.fallbackArg, ~level) => { let beaconApiCall = data => { if data->Js.Array2.length > 0 { - let logData = - [("data", data->Js.Array2.map(OrcaLogger.logFileToObj)->Js.Json.array)] - ->Js.Dict.fromArray - ->Js.Json.object_ - ->Js.Json.stringify + let logData = data->Js.Array2.map(OrcaLogger.logFileToObj)->Js.Json.array->Js.Json.stringify Window.sendBeacon(GlobalVars.logEndpoint, logData) } } @@ -131,7 +127,7 @@ module ErrorCard = { platform: Window.platform, userAgent: Window.userAgent, appId: "", - eventName: "SDK_CRASH", + eventName: SDK_CRASH, latency: "", paymentMethod: "", firstEvent: false, diff --git a/src/orca-log-catcher/OrcaLogger.res b/src/orca-log-catcher/OrcaLogger.res index 4db491587..a123e362c 100644 --- a/src/orca-log-catcher/OrcaLogger.res +++ b/src/orca-log-catcher/OrcaLogger.res @@ -27,6 +27,7 @@ type eventName = | PAYPAL_SDK_FLOW | APP_INITIATED | APP_REINITIATED + | LOG_INITIATED | LOADER_CALLED | ORCA_ELEMENTS_CALLED | PAYMENT_OPTIONS_PROVIDED @@ -52,7 +53,6 @@ type eventName = | REDIRECTING_USER | DISPLAY_BANK_TRANSFER_INFO_PAGE | DISPLAY_QR_CODE_INFO_PAGE - | DYNAMIC_FIELDS_RENDER | PAYMENT_METHODS_RESPONSE let eventNameToStrMapper = eventName => { @@ -82,6 +82,7 @@ let eventNameToStrMapper = eventName => { | PAYPAL_SDK_FLOW => "PAYPAL_SDK_FLOW" | APP_INITIATED => "APP_INITIATED" | APP_REINITIATED => "APP_REINITIATED" + | LOG_INITIATED => "LOG_INITIATED" | LOADER_CALLED => "LOADER_CALLED" | ORCA_ELEMENTS_CALLED => "ORCA_ELEMENTS_CALLED" | PAYMENT_OPTIONS_PROVIDED => "PAYMENT_OPTIONS_PROVIDED" @@ -107,7 +108,6 @@ let eventNameToStrMapper = eventName => { | REDIRECTING_USER => "REDIRECTING_USER" | DISPLAY_BANK_TRANSFER_INFO_PAGE => "DISPLAY_BANK_TRANSFER_INFO_PAGE" | DISPLAY_QR_CODE_INFO_PAGE => "DISPLAY_QR_CODE_INFO_PAGE" - | DYNAMIC_FIELDS_RENDER => "DYNAMIC_FIELDS_RENDER" | PAYMENT_METHODS_RESPONSE => "PAYMENT_METHODS_RESPONSE" } } @@ -138,7 +138,7 @@ type logFile = { browserName: string, browserVersion: string, userAgent: string, - eventName: string, + eventName: eventName, latency: string, firstEvent: bool, paymentMethod: string, @@ -257,7 +257,7 @@ let logFileToObj = logFile => { ("app_id", logFile.appId->Js.Json.string), ("platform", logFile.platform->convertToScreamingSnakeCase->Js.Json.string), ("user_agent", logFile.userAgent->Js.Json.string), - ("event_name", logFile.eventName->Js.Json.string), + ("event_name", logFile.eventName->eventNameToStrMapper->Js.Json.string), ("browser_name", logFile.browserName->convertToScreamingSnakeCase->Js.Json.string), ("browser_version", logFile.browserVersion->Js.Json.string), ("latency", logFile.latency->Js.Json.string), @@ -408,6 +408,9 @@ let make = ( | None => GlobalVars.repoName } + let events = ref(Js.Dict.empty()) + let eventsCounter = ref(Js.Dict.empty()) + let timeOut = ref(None) let merchantId = getRefFromOption(merchantId) @@ -421,8 +424,25 @@ let make = ( metadata := value } + let calculateAndUpdateCounterHook = eventName => { + let updatedCounter = switch eventsCounter.contents->Js.Dict.get(eventName) { + | Some(num) => num + 1 + | None => 1 + } + eventsCounter.contents->Js.Dict.set(eventName, updatedCounter) + updatedCounter + } + let conditionalLogPush = (log: logFile) => { - if GlobalVars.enableLogging { + let maxLogsPushedPerEventName = GlobalVars.maxLogsPushedPerEventName + let conditionalEventName = switch log.eventName { + | INPUT_FIELD_CHANGED => log.value // to enforce rate limiting for each input field independently + | _ => "" + } + let eventName = log.eventName->eventNameToStrMapper ++ conditionalEventName + + let counter = eventName->calculateAndUpdateCounterHook + if GlobalVars.enableLogging && counter <= maxLogsPushedPerEventName { switch loggingLevel { | DEBUG => log->Js.Array2.push(mainLogFile, _)->ignore | INFO => @@ -442,11 +462,7 @@ let make = ( let beaconApiCall = data => { if data->Js.Array2.length > 0 { - let logData = - [("data", data->Js.Array2.map(logFileToObj)->Js.Json.array)] - ->Js.Dict.fromArray - ->Js.Json.object_ - ->Js.Json.stringify + let logData = data->Js.Array2.map(logFileToObj)->Js.Json.array->Js.Json.stringify Window.sendBeacon(GlobalVars.logEndpoint, logData) } } @@ -456,8 +472,6 @@ let make = ( clientSecret := value } - let events = ref(Js.Dict.empty()) - let rec sendLogs = () => { switch timeOut.contents { | Some(val) => { @@ -475,16 +489,16 @@ let make = ( let checkForPriorityEvents = (arrayOfLogs: array) => { let priorityEventNames = [ - "APP_RENDERED", - "ORCA_ELEMENTS_CALLED", - "PAYMENT_DATA_FILLED", - "PAYMENT_ATTEMPT", - "CONFIRM_CALL", - "SDK_CRASH", - "REDIRECTING_USER", - "DISPLAY_BANK_TRANSFER_INFO_PAGE", - "DISPLAY_QR_CODE_INFO_PAGE", - "SESSIONS_CALL", + APP_RENDERED, + ORCA_ELEMENTS_CALLED, + PAYMENT_DATA_FILLED, + PAYMENT_ATTEMPT, + CONFIRM_CALL, + SDK_CRASH, + REDIRECTING_USER, + DISPLAY_BANK_TRANSFER_INFO_PAGE, + DISPLAY_QR_CODE_INFO_PAGE, + SESSIONS_CALL, ] arrayOfLogs ->Js.Array2.find(log => { @@ -511,19 +525,20 @@ let make = ( let calculateLatencyHook = (~eventName, ~type_="", ()) => { let currentTimestamp = Js.Date.now() let latency = switch eventName { - | "PAYMENT_ATTEMPT" => { - let appRenderedTimestamp = events.contents->Js.Dict.get("APP_RENDERED") + | PAYMENT_ATTEMPT => { + let appRenderedTimestamp = events.contents->Js.Dict.get(APP_RENDERED->eventNameToStrMapper) switch appRenderedTimestamp { | Some(float) => currentTimestamp -. float | _ => -1. } } - | "RETRIEVE_CALL" - | "CONFIRM_CALL" - | "SESSIONS_CALL" - | "PAYMENT_METHODS_CALL" - | "CUSTOMER_PAYMENT_METHODS_CALL" => { - let logRequestTimestamp = events.contents->Js.Dict.get(eventName ++ "_INIT") + | RETRIEVE_CALL + | CONFIRM_CALL + | SESSIONS_CALL + | PAYMENT_METHODS_CALL + | CUSTOMER_PAYMENT_METHODS_CALL => { + let logRequestTimestamp = + events.contents->Js.Dict.get(eventName->eventNameToStrMapper ++ "_INIT") switch (logRequestTimestamp, type_) { | (Some(_), "request") => 0. | (Some(float), _) => currentTimestamp -. float @@ -547,7 +562,7 @@ let make = ( ) => { let eventNameStr = eventName->eventNameToStrMapper let firstEvent = events.contents->Js.Dict.get(eventNameStr)->Belt.Option.isNone - let latency = calculateLatencyHook(~eventName=eventNameStr, ()) + let latency = calculateLatencyHook(~eventName, ()) let localTimestamp = timestamp->Belt.Option.getWithDefault(Js.Date.now()->Belt.Float.toString) let localTimestampFloat = localTimestamp->Belt.Float.fromString->Belt.Option.getWithDefault(Js.Date.now()) @@ -569,7 +584,7 @@ let make = ( platform: Window.platform, userAgent: Window.userAgent, appId: "", - eventName: eventNameStr, + eventName, latency, paymentMethod, firstEvent, @@ -600,7 +615,7 @@ let make = ( ) => { let eventNameStr = eventName->eventNameToStrMapper let firstEvent = events.contents->Js.Dict.get(eventNameStr)->Belt.Option.isNone - let latency = calculateLatencyHook(~eventName=eventNameStr, ~type_, ()) + let latency = calculateLatencyHook(~eventName, ~type_, ()) let localTimestamp = timestamp->Belt.Option.getWithDefault(Js.Date.now()->Belt.Float.toString) let localTimestampFloat = localTimestamp->Belt.Float.fromString->Belt.Option.getWithDefault(Js.Date.now()) @@ -628,7 +643,7 @@ let make = ( platform: Window.platform, userAgent: Window.userAgent, appId: "", - eventName: eventNameStr, + eventName, latency, paymentMethod, firstEvent, @@ -652,7 +667,7 @@ let make = ( ) => { let eventNameStr = eventName->eventNameToStrMapper let firstEvent = events.contents->Js.Dict.get(eventNameStr)->Belt.Option.isNone - let latency = calculateLatencyHook(~eventName=eventNameStr, ()) + let latency = calculateLatencyHook(~eventName, ()) let localTimestamp = timestamp->Belt.Option.getWithDefault(Js.Date.now()->Belt.Float.toString) let localTimestampFloat = localTimestamp->Belt.Float.fromString->Belt.Option.getWithDefault(Js.Date.now()) @@ -674,7 +689,7 @@ let make = ( platform: Window.platform, userAgent: Window.userAgent, appId: "", - eventName: eventNameStr, + eventName, latency, paymentMethod, firstEvent, @@ -687,8 +702,9 @@ let make = ( } let setLogInitiated = () => { - let eventName = "LOG_INITIATED" - let firstEvent = events.contents->Js.Dict.get(eventName)->Belt.Option.isNone + let eventName: eventName = LOG_INITIATED + let eventNameStr = eventName->eventNameToStrMapper + let firstEvent = events.contents->Js.Dict.get(eventNameStr)->Belt.Option.isNone let latency = calculateLatencyHook(~eventName, ()) { logType: INFO, @@ -717,7 +733,7 @@ let make = ( ->conditionalLogPush ->ignore checkLogSizeAndSendData() - events.contents->Js.Dict.set(eventName, Js.Date.now()) + events.contents->Js.Dict.set(eventNameStr, Js.Date.now()) } let handleBeforeUnload = _event => { diff --git a/webpack.common.js b/webpack.common.js index f98be8ba0..c82870387 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -65,8 +65,8 @@ if (envBackendUrl === undefined) { let logEndpoint = sdkEnv === "prod" - ? "https://api.hyperswitch.io/sdk-logs" - : "https://sandbox.juspay.io/godel/analytics"; + ? "https://api.hyperswitch.io/logs/sdk" + : "https://sandbox.hyperswitch.io/logs/sdk"; // Set this to true to enable logging let enableLogging = true; @@ -74,6 +74,9 @@ let enableLogging = true; // Choose from DEBUG, INFO, WARNING, ERROR, SILENT let loggingLevel = "DEBUG"; +// Maximum logs emitted for a particular event, to rate limit logs +let maxLogsPushedPerEventName = 100; + module.exports = (publicPath = "auto") => { let entries = { app: "./index.js", @@ -123,6 +126,7 @@ module.exports = (publicPath = "auto") => { sentryScriptUrl: JSON.stringify(process.env.SENTRY_SCRIPT_URL), enableLogging: JSON.stringify(enableLogging), loggingLevel: JSON.stringify(loggingLevel), + maxLogsPushedPerEventName: JSON.stringify(maxLogsPushedPerEventName), }), new HtmlWebpackPlugin({ inject: false,