diff --git a/ios/NotificationServiceExtension/NotificationService.swift b/ios/NotificationServiceExtension/NotificationService.swift index e489cb368d17..b588c6be1d0f 100644 --- a/ios/NotificationServiceExtension/NotificationService.swift +++ b/ios/NotificationServiceExtension/NotificationService.swift @@ -8,12 +8,18 @@ import AirshipServiceExtension import os.log import Intents +import AppLogs class NotificationService: UANotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? let log = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "com.expensify.chat.dev.NotificationServiceExtension", category: "NotificationService") + let appLogs: AppLogs = .init() + + deinit { + appLogs.forwardLogsTo(appGroup: "group.com.expensify.new") + } override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { os_log("[NotificationService] didReceive() - received notification", log: log) @@ -42,7 +48,7 @@ class NotificationService: UANotificationServiceExtension { do { notificationData = try parsePayload(notificationContent: notificationContent) } catch ExpError.runtimeError(let errorMessage) { - os_log("[NotificationService] configureCommunicationNotification() - couldn't parse the payload '%@'", log: log, type: .error, errorMessage) + os_log("[NotificationService] configureCommunicationNotification() - couldn't parse the payload '%{public}@'", log: log, type: .error, errorMessage) contentHandler(notificationContent) return } catch { @@ -212,7 +218,7 @@ class NotificationService: UANotificationServiceExtension { let data = try Data(contentsOf: url) return INImage(imageData: data) } catch { - os_log("[NotificationService] fetchINImage() - failed to fetch avatar. reportActionID: %@", log: self.log, type: .error, reportActionID) + os_log("[NotificationService] fetchINImage() - failed to fetch avatar. reportActionID: %{public}@", log: self.log, type: .error, reportActionID) return nil } } diff --git a/ios/Podfile b/ios/Podfile index e807089c26b9..4d139711ef01 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -119,6 +119,7 @@ end target 'NotificationServiceExtension' do pod 'AirshipServiceExtension' + pod 'AppLogs', :path => '../node_modules/react-native-app-logs/AppLogsPod' end pod 'FullStory', :http => 'https://ios-releases.fullstory.com/fullstory-1.52.0-xcframework.tar.gz' \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1242ab7a5a39..31a24d0f248d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -26,6 +26,7 @@ PODS: - AppAuth/Core (1.7.5) - AppAuth/ExternalUserAgent (1.7.5): - AppAuth/Core + - AppLogs (0.1.0) - boost (1.84.0) - DoubleConversion (1.1.6) - EXAV (14.0.7): @@ -1564,6 +1565,27 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-app-logs (0.3.1): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-blob-util (0.19.4): - DoubleConversion - glog @@ -2702,6 +2724,7 @@ PODS: DEPENDENCIES: - AirshipServiceExtension + - AppLogs (from `../node_modules/react-native-app-logs/AppLogsPod`) - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - EXAV (from `../node_modules/expo-av/ios`) @@ -2751,6 +2774,7 @@ DEPENDENCIES: - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) - "react-native-airship (from `../node_modules/@ua/react-native-airship`)" + - react-native-app-logs (from `../node_modules/react-native-app-logs`) - react-native-blob-util (from `../node_modules/react-native-blob-util`) - "react-native-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)" - react-native-config (from `../node_modules/react-native-config`) @@ -2864,6 +2888,8 @@ SPEC REPOS: - Turf EXTERNAL SOURCES: + AppLogs: + :path: "../node_modules/react-native-app-logs/AppLogsPod" boost: :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" DoubleConversion: @@ -2959,6 +2985,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" react-native-airship: :path: "../node_modules/@ua/react-native-airship" + react-native-app-logs: + :path: "../node_modules/react-native-app-logs" react-native-blob-util: :path: "../node_modules/react-native-blob-util" react-native-cameraroll: @@ -3109,6 +3137,7 @@ SPEC CHECKSUMS: AirshipFrameworkProxy: dbd862dc6fb21b13e8b196458d626123e2a43a50 AirshipServiceExtension: 9c73369f426396d9fb9ff222d86d842fac76ba46 AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa + AppLogs: 3bc4e9b141dbf265b9464409caaa40416a9ee0e0 boost: 26992d1adf73c1c7676360643e687aee6dda994b DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 EXAV: afa491e598334bbbb92a92a2f4dd33d7149ad37f @@ -3184,6 +3213,7 @@ SPEC CHECKSUMS: React-Mapbuffer: 1c08607305558666fd16678b85ef135e455d5c96 React-microtasksnativemodule: f13f03163b6a5ec66665dfe80a0df4468bb766a6 react-native-airship: e10f6823d8da49bbcb2db4bdb16ff954188afccc + react-native-app-logs: b8a104816aafc78cd0965e923452de88dcf8ec67 react-native-blob-util: 221c61c98ae507b758472ac4d2d489119d1a6c44 react-native-cameraroll: 478a0c1fcdd39f08f6ac272b7ed06e92b2c7c129 react-native-config: 742a9e0a378a78d0eaff1fb3477d8c0ae222eb51 @@ -3261,6 +3291,6 @@ SPEC CHECKSUMS: VisionCamera: c6c8aa4b028501fc87644550fbc35a537d4da3fb Yoga: a1d7895431387402a674fd0d1c04ec85e87909b8 -PODFILE CHECKSUM: a07e55247056ec5d84d1af31d694506efff3cfe2 +PODFILE CHECKSUM: 15e2f095b9c80d658459723edf84005a6867debf COCOAPODS: 1.15.2 diff --git a/jest/setup.ts b/jest/setup.ts index 6901ad3c66f3..7dbe91c32fda 100644 --- a/jest/setup.ts +++ b/jest/setup.ts @@ -1,5 +1,6 @@ /* eslint-disable max-classes-per-file */ import '@shopify/flash-list/jestSetup'; +import type * as RNAppLogs from 'react-native-app-logs'; import 'react-native-gesture-handler/jestSetup'; import type * as RNKeyboardController from 'react-native-keyboard-controller'; import mockStorage from 'react-native-onyx/dist/storage/__mocks__'; @@ -75,6 +76,8 @@ jest.mock('react-native-reanimated', () => ({ jest.mock('react-native-keyboard-controller', () => require('react-native-keyboard-controller/jest')); +jest.mock('react-native-app-logs', () => require('react-native-app-logs/jest')); + jest.mock('@src/libs/actions/Timing', () => ({ start: jest.fn(), end: jest.fn(), diff --git a/package-lock.json b/package-lock.json index 5127987d8ec2..144097c558ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,6 +76,7 @@ "react-map-gl": "^7.1.3", "react-native": "0.75.2", "react-native-android-location-enabler": "^2.0.1", + "react-native-app-logs": "git+https://github.com/margelo/react-native-app-logs#7e9c311bffdc6a9eeb69d90d30ead47e01c3552a", "react-native-blob-util": "0.19.4", "react-native-collapsible": "^1.6.2", "react-native-config": "1.5.3", @@ -34418,6 +34419,18 @@ "prop-types": "^15.7.2" } }, + "node_modules/react-native-app-logs": { + "version": "0.3.1", + "resolved": "git+ssh://git@github.com/margelo/react-native-app-logs.git#7e9c311bffdc6a9eeb69d90d30ead47e01c3552a", + "integrity": "sha512-GFZFbUe9bUIbuH2zTAS7JAXCAIYnyf4cTnsz6pSzYCl3F+nF+O3fRa5ZM8P7zr+wTG7fZoVs0b6XFfcFUcxY2A==", + "workspaces": [ + "example" + ], + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-blob-util": { "version": "0.19.4", "license": "MIT", diff --git a/package.json b/package.json index 028812ae28d4..65cd35815f3e 100644 --- a/package.json +++ b/package.json @@ -131,6 +131,7 @@ "react-map-gl": "^7.1.3", "react-native": "0.75.2", "react-native-android-location-enabler": "^2.0.1", + "react-native-app-logs": "git+https://github.com/margelo/react-native-app-logs#7e9c311bffdc6a9eeb69d90d30ead47e01c3552a", "react-native-blob-util": "0.19.4", "react-native-collapsible": "^1.6.2", "react-native-config": "1.5.3", diff --git a/src/libs/Log.ts b/src/libs/Log.ts index 72673b8d3f79..2ccbd1d37882 100644 --- a/src/libs/Log.ts +++ b/src/libs/Log.ts @@ -3,6 +3,7 @@ /* eslint-disable rulesdir/no-api-in-views */ import {Logger} from 'expensify-common'; +import AppLogs from 'react-native-app-logs'; import Onyx from 'react-native-onyx'; import type {Merge} from 'type-fest'; import CONST from '@src/CONST'; @@ -82,4 +83,21 @@ const Log = new Logger({ }); timeout = setTimeout(() => Log.info('Flushing logs older than 10 minutes', true, {}, true), 10 * 60 * 1000); +AppLogs.configure({appGroupName: 'group.com.expensify.new', interval: -1}); +AppLogs.registerHandler({ + filter: '[NotificationService]', + handler: ({filter, logs}) => { + logs.forEach((log) => { + // Both native and JS logs are captured by the filter so we replace the filter before logging to avoid an infinite loop + const message = `[PushNotification] ${log.message.replace(filter, 'NotificationService -')}`; + + if (log.level === 'error') { + Log.hmmm(message); + } else { + Log.info(message); + } + }); + }, +}); + export default Log;