Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: reduce log size #2680

Merged
merged 43 commits into from
Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
2ea8bcd
build(deps): react-native-email-link, react-native-mail and react-nat…
MuckT Jul 8, 2022
c7d6d29
fix: delete log file if older than 28 days
MuckT Jul 8, 2022
99bea78
fix: lint errors
MuckT Jul 8, 2022
352d2c2
Merge branch 'main' into tomm/reduce-log-size
MuckT Jul 8, 2022
1512eac
feat: store logs by day and combine when sending to support
MuckT Jul 12, 2022
242eccb
test: update support contact tests to use mock combined logs
MuckT Jul 12, 2022
532124f
fix: lint errors use const instead of let
MuckT Jul 12, 2022
fd20d1f
Merge branch 'main' into tomm/reduce-log-size
MuckT Jul 12, 2022
14dfc05
Merge branch 'main' into tomm/reduce-log-size
MuckT Jul 13, 2022
80e8b8b
feat: separate function for log cleanup and create log file in writeLog
MuckT Jul 13, 2022
91d1e4a
fix: add try catch for writeLog
MuckT Jul 13, 2022
854436b
fix: lint error promises must be handled appropriately
MuckT Jul 13, 2022
eb39621
test(logger): add unit tests
MuckT Jul 13, 2022
ddeaa9e
fix: lint errors
MuckT Jul 13, 2022
367a088
Merge branch 'main' into tomm/reduce-log-size
MuckT Jul 13, 2022
a86a7a7
style: use error consistently
MuckT Jul 15, 2022
f8b8426
fix: use for...of instead of forEach for easier awaits
MuckT Jul 15, 2022
76e1ea1
feat: add logFile path to console debug statement
MuckT Jul 15, 2022
ae68c21
fix: build error from createCombinedLogs
MuckT Jul 15, 2022
b8e398d
feat: create readFile util to read files in chunks for lower memory u…
MuckT Jul 15, 2022
1790afb
Merge branch 'main' into tomm/reduce-log-size
MuckT Jul 15, 2022
d728f24
fix: add missing logger import
MuckT Jul 15, 2022
34130b3
Merge branch 'main' into tomm/reduce-log-size
MuckT Jul 15, 2022
8bd70d0
feat: upload multiple attachments and save logs up to 60 days
MuckT Jul 17, 2022
7ecc4bb
Merge branch 'main' into tomm/reduce-log-size
MuckT Jul 18, 2022
82a3162
fix: error tag and bubble up errors from readFileChunked
MuckT Jul 18, 2022
c7f0578
Merge branch 'main' into tomm/reduce-log-size
MuckT Jul 18, 2022
2c5ab81
Merge branch 'main' into tomm/reduce-log-size
MuckT Jul 19, 2022
b29d0f5
fix: remove type casting
MuckT Jul 20, 2022
bdc449d
feat: handle os specific paths and move date-fs formats to reusable f…
MuckT Jul 20, 2022
8290ca9
feat: avoid log file copies and deletes to tmp directory on iOS
MuckT Jul 20, 2022
0be49fc
Merge branch 'main' into tomm/reduce-log-size
MuckT Jul 20, 2022
6c0725f
Merge branch 'main' into tomm/reduce-log-size
MuckT Jul 20, 2022
e56767d
fix: remove minSetInProgress and use inline setTimeout
MuckT Jul 20, 2022
b043de5
fix: get attachment path to use with sendEmailWithNonNativeApp
MuckT Jul 20, 2022
ba6a33d
fix: handle if attachments are undefined
MuckT Jul 20, 2022
2de225c
Merge branch 'main' into tomm/reduce-log-size
MuckT Jul 20, 2022
6bd994f
test: mock Logger.getCurrentLogFileName
MuckT Jul 20, 2022
cb81575
Merge branch 'main' into tomm/reduce-log-size
MuckT Jul 20, 2022
c71a97b
Merge branch 'main' into tomm/reduce-log-size
MuckT Jul 21, 2022
ded87a8
fix: typing for attachments
MuckT Jul 21, 2022
c3b9650
fix: lint no-unsafe-finally
MuckT Jul 21, 2022
4e1055a
Merge branch 'main' into tomm/reduce-log-size
MuckT Jul 21, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion __mocks__/src/utils/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ module.exports = {
...jest.requireActual('src/utils/Logger'),
default: {
...jest.requireActual('src/utils/Logger').default,
createCombinedLogs: jest.fn(),
getLogsToAttach: jest.fn(),
},
}
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Text, TextInput } from 'react-native'
const SENTRY_ENABLED = stringToBoolean(Config.SENTRY_ENABLED || 'false')

Logger.overrideConsoleLogs()
Logger.cleanupOldLogs()

const defaultErrorHandler = ErrorUtils.getGlobalHandler()
const customErrorHandler = (e, isFatal) => {
Expand Down
8 changes: 4 additions & 4 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ PODS:
- React-Core
- react-native-keep-awake (4.0.0):
- React-Core
- react-native-mail (6.0.0):
- react-native-mail (6.1.1):
- React-Core
- react-native-netinfo (5.8.0):
- React-Core
Expand Down Expand Up @@ -599,7 +599,7 @@ PODS:
- Firebase/Storage (= 7.11.0)
- React-Core
- RNFBApp
- RNFS (2.16.6):
- RNFS (2.20.0):
- React-Core
- RNGestureHandler (1.9.0):
- React-Core
Expand Down Expand Up @@ -1092,7 +1092,7 @@ SPEC CHECKSUMS:
react-native-fast-crypto: db61bcb2d1224a180449b886f27426a67d7628c0
react-native-flipper: b9e2e817604af8da0d5a9ba20a8516e780e30f3c
react-native-keep-awake: 6c078705f3fb2586963b13588d31148adc6bd128
react-native-mail: 88305252f4c3fb0157015ff8a7ac9c41b321a0dd
react-native-mail: 8fdcd3aef007c33a6877a18eb4cf7447a1d4ce4a
react-native-netinfo: f3f52e6d92372003b78d5cafa3557797c7beee47
react-native-plaid-link-sdk: 33cb904b956dc3b56799ac3027de8aa6fbb4b0ab
react-native-randombytes: b6677f7d495c27e9ee0dbd77ebc97b3c59173729
Expand Down Expand Up @@ -1132,7 +1132,7 @@ SPEC CHECKSUMS:
RNFBMessaging: b05aa2a76ed2b9fd50f05f036c96b26189a1fce2
RNFBRemoteConfig: e8d462f46e1759a5921d0e4b76d0ab89aef9baee
RNFBStorage: 48f869205eb5537749cdd4f53689a526728421f8
RNFS: 7161a36db9bbdaab96d91fff660820f94e29ca58
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNGestureHandler: 9b7e605a741412e20e13c512738a31bd1611759b
RNImageCropPicker: 9e0bf18cf4184a846fed55747c8e622208b39947
RNInAppBrowser: 48b95ba7a4eaff5cc223bca338d3e319561dbd1b
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,19 +147,19 @@
"react-native-config": "https://github.com/luggit/react-native-config#2f68b94",
"react-native-contacts": "https://github.com/celo-org/react-native-contacts#9940121",
"react-native-device-info": "^8.3.1",
"react-native-email-link": "^1.12.2",
"react-native-email-link": "^1.14.0",
"react-native-exit-app": "https://github.com/wumke/react-native-exit-app#5a022a7",
"react-native-fast-crypto": "^2.0.0",
"react-native-flag-secure-android": "https://github.com/kristiansorens/react-native-flag-secure-android#e234251",
"react-native-fs": "git+https://github.com/celo-org/react-native-fs#04669ac",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^1.9.0",
"react-native-google-safetynet": "https://github.com/celo-org/react-native-google-safetynet#8a0355f",
"react-native-image-crop-picker": "^0.35.1",
"react-native-inappbrowser-reborn": "^3.5.1",
"react-native-keep-awake": "^4.0.0",
"react-native-keychain": "8.0.0",
"react-native-localize": "^2.1.7",
"react-native-mail": "^6.0.0",
"react-native-mail": "^6.1.1",
"react-native-modal": "^13.0.1",
"react-native-ntp-client": "^1.0.3",
"react-native-permissions": "^3.0.3",
Expand Down
24 changes: 17 additions & 7 deletions src/account/SupportContact.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import SupportContact from 'src/account/SupportContact'
import { APP_NAME, CELO_SUPPORT_EMAIL_ADDRESS } from 'src/brandingConfig'
import i18n from 'src/i18n'
import { Screens } from 'src/navigator/Screens'
import Logger from 'src/utils/Logger'
import { createMockStore, flushMicrotasksQueue, getMockStackScreenProps } from 'test/utils'

const mockScreenProps = getMockStackScreenProps(Screens.SupportContact)
Expand All @@ -22,6 +23,21 @@ describe('Contact', () => {
})

it('submits email with logs', async () => {
const mockedLogAttachments = Logger.getLogsToAttach as jest.Mock
const logAttachments = [
{
path: 'logs/log1.txt',
type: 'text',
name: 'log1.txt',
},
{
path: 'logs/log2.txt',
type: 'text',
name: 'log2.txt',
},
]
mockedLogAttachments.mockResolvedValue(logAttachments)

const { getByTestId } = render(
<Provider store={createMockStore({})}>
<SupportContact {...mockScreenProps} />
Expand All @@ -40,13 +56,7 @@ describe('Contact', () => {
'Test Message<br/><br/><b>{"version":"0.0.1","buildNumber":"1","apiLevel":-1,"deviceId":"unknown","address":"0x0000000000000000000000000000000000007e57","sessionId":"","numberVerified":false,"network":"alfajores"}</b><br/><br/><b>Support logs are attached...</b>',
recipients: [CELO_SUPPORT_EMAIL_ADDRESS],
subject: i18n.t('supportEmailSubject', { appName: APP_NAME, user: '+1415555XXXX' }),
attachments: [
{
path: '__EXTERNAL_DIRECTORY_PATH__/rn_logs.txt',
type: 'text',
name: '',
},
],
attachments: logAttachments,
}),
expect.any(Function)
)
Expand Down
53 changes: 14 additions & 39 deletions src/account/SupportContact.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { anonymizedPhone } from '@celo/utils/lib/phoneNumbers'
import { StackScreenProps } from '@react-navigation/stack'
import path from 'path'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ActivityIndicator, Platform, ScrollView, StyleSheet, Text, View } from 'react-native'
import { ActivityIndicator, ScrollView, StyleSheet, Text, View } from 'react-native'
import DeviceInfo from 'react-native-device-info'
import * as RNFS from 'react-native-fs'
import { useDispatch, useSelector } from 'react-redux'
import { Email, sendEmail } from 'src/account/emailSender'
import { e164NumberSelector } from 'src/account/selectors'
Expand All @@ -17,7 +15,6 @@ import KeyboardSpacer from 'src/components/KeyboardSpacer'
import Switch from 'src/components/Switch'
import TextInput from 'src/components/TextInput'
import { CELO_SUPPORT_EMAIL_ADDRESS, DEFAULT_TESTNET } from 'src/config'
import i18n from 'src/i18n'
import { navigateBack } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { StackParamList } from 'src/navigator/types'
Expand All @@ -28,28 +25,6 @@ import { currentAccountSelector } from 'src/web3/selectors'

type Props = StackScreenProps<StackParamList, Screens.SupportContact>

async function exportLogs() {
try {
Logger.showMessage(i18n.t('supportExportLogsStart'))
const logsPath = Logger.getReactNativeLogsFilePath()

// For now we need to export to a world-readable directory on Android
// TODO: use the FileProvider approach so we don't need to do this.
// See https://developer.android.com/reference/androidx/core/content/FileProvider
// and https://github.com/chirag04/react-native-mail/blame/340618e4ef7f21a29d739d4180c2a267a14093d3/android/src/main/java/com/chirag/RNMail/RNMailModule.java#L106
if (Platform.OS === 'android') {
const publicPath = `${RNFS.ExternalDirectoryPath}/${path.basename(logsPath)}`
await RNFS.copyFile(logsPath, publicPath)
return publicPath
}

return logsPath
} catch (e) {
Logger.showError(i18n.t('supportExportLogsError', { error: e }))
return false
}
}

function SupportContact({ route }: Props) {
const { t } = useTranslation()
const [message, setMessage] = useState('')
Expand All @@ -73,6 +48,11 @@ function SupportContact({ route }: Props) {
dispatch(showMessage(t('contactSuccess')))
}

// Used to prevent flickering of the activity indicator on quick uploads
const minSetInProgress = (setTo: boolean) => {
MuckT marked this conversation as resolved.
Show resolved Hide resolved
setTimeout(() => setInProgress(setTo), 1000)
}

const onPressSendEmail = useCallback(async () => {
setInProgress(true)
const deviceInfo = {
Expand All @@ -92,26 +72,21 @@ function SupportContact({ route }: Props) {
body: `${message}<br/><br/><b>${JSON.stringify(deviceInfo)}</b>`,
isHTML: true,
}
let logsPath: string | false = false
// TODO(Tom): use correct typing
MuckT marked this conversation as resolved.
Show resolved Hide resolved
let attachments: Array<Email['attachments']> | any
MuckT marked this conversation as resolved.
Show resolved Hide resolved
if (attachLogs) {
logsPath = await exportLogs()
if (logsPath) {
email.attachments = [
{
path: logsPath, // The absolute path of the file from which to read data.
type: 'text', // Mime Type: jpg, png, doc, ppt, html, pdf, csv
name: '', // Optional: Custom filename for attachment
},
]
attachments = await Logger.getLogsToAttach()
if (attachments) {
email.attachments = attachments
email.body += (email.body ? '<br/><br/>' : '') + '<b>Support logs are attached...</b>'
}
}
setInProgress(false)
minSetInProgress(false)
try {
await sendEmail(email, deviceInfo, logsPath)
await sendEmail(email, deviceInfo, attachments[0].path ?? false)
MuckT marked this conversation as resolved.
Show resolved Hide resolved
navigateBackAndToast()
} catch (error) {
Logger.error('SupportContact', 'Error while sending logs to support', error)
Logger.error('SupportContact', 'Error while sending logs to support', error as Error)
MuckT marked this conversation as resolved.
Show resolved Hide resolved
}
}, [message, attachLogs, e164PhoneNumber])

Expand Down
4 changes: 2 additions & 2 deletions src/account/emailSender.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { openComposer } from 'react-native-email-link'
import * as RNFS from 'react-native-fs'
import Mailer from 'react-native-mail'
import { CELO_SUPPORT_EMAIL_ADDRESS } from 'src/config'
import Logger from 'src/utils/Logger'
import { readFileChunked } from 'src/utils/readFile'

export interface Email {
subject: string
Expand All @@ -24,7 +24,7 @@ export async function sendEmailWithNonNativeApp(
) {
try {
const supportLogsMessage = logsPath
? `Support logs: ${!logsPath || (await RNFS.readFile(logsPath))}`
? `Support logs: ${!logsPath || (await readFileChunked(logsPath))}`
: ''
await openComposer({
to: CELO_SUPPORT_EMAIL_ADDRESS,
Expand Down
2 changes: 1 addition & 1 deletion src/app/Debug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class Debug extends React.Component<RootState, State> {
}

updateLogs = async () => {
const reactNativeLogs = await Logger.getLogs()
const reactNativeLogs = await Logger.getMonthLogs()
this.setState({
reactNativeLogs: reactNativeLogs || 'Not Found',
})
Expand Down
86 changes: 86 additions & 0 deletions src/utils/Logger.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* eslint no-console: 0 */
// no-console disabled as we are testing console logs are overwritten by the logger
import 'react-native'
import { Platform } from 'react-native'
import { ONE_DAY_IN_MILLIS } from 'src/utils/time'
import Logger from './Logger'

const mockData = [
{
// ctime is 61 days ago
ctime: new Date(new Date().getTime() - ONE_DAY_IN_MILLIS * 61),
name: 'toDelete.txt',
path: '__TEMPORARY_DIRECTORY_PATH__/rn_logs/toDelete.txt',
size: 5318,
isFile: true,
isDirectory: false,
},
{
ctime: new Date(),
name: 'toSave.txt',
path: '__TEMPORARY_DIRECTORY_PATH__/rn_logs/toSave.txt',
size: 5318,
isFile: true,
isDirectory: false,
},
]

jest.mock('react-native-fs', () => {
return {
readDir: jest.fn(() => Promise.resolve(mockData)),
stat: jest
.fn()
.mockImplementation((filePath) =>
Promise.resolve(mockData.find((file) => file.path === filePath))
),
exists: jest.fn(),
unlink: jest.fn(() => Promise.resolve()),
writeFile: jest.fn(() => Promise.resolve()),
ExternalDirectoryPath: '__EXTERNAL_DIRECTORY_PATH__',
TemporaryDirectoryPath: '__TEMPORARY_DIRECTORY_PATH__',
}
})

describe('utils/Logger', () => {
it('Logger overrides console.debug', () => {
console.debug = jest.fn()
Logger.debug('Test/Debug', 'Test message #1', 'Test message #2')
expect(console.debug).toBeCalledTimes(1)
expect(console.debug).toHaveBeenCalledWith('Test/Debug/Test message #1, Test message #2')
})

it('Logger overrides console.info', () => {
console.info = jest.fn()
Logger.info('Test/Info', 'Test message #1', 'Test message #2')
expect(console.info).toBeCalledTimes(1)
expect(console.info).toHaveBeenCalledWith('Test/Info/Test message #1, Test message #2')
})

it('Logger.warn pipes to console.info', () => {
console.info = jest.fn()
Logger.warn('Test/Warn', 'Test message #1', 'Test message #2')
expect(console.info).toBeCalledTimes(1)
expect(console.info).toHaveBeenCalledWith('Test/Warn/Test message #1, Test message #2')
})

it('Returns combined logs file path iOS', () => {
// mock
Platform.OS = 'ios'
expect(Logger.getCombinedLogsFilePath()).toBe('__TEMPORARY_DIRECTORY_PATH__/rn_logs.txt')
})

it('Returns combined logs file path Android', () => {
// mock
Platform.OS = 'android'
expect(Logger.getCombinedLogsFilePath()).toBe('__EXTERNAL_DIRECTORY_PATH__/rn_logs.txt')
})

it('Cleans up old logs', async () => {
console.debug = jest.fn()
await Logger.cleanupOldLogs()
expect(console.debug).toHaveBeenCalledTimes(1)
expect(console.debug).toHaveBeenCalledWith(
'Logger/cleanupOldLogs/Deleting React Native log file older than 60 days, __TEMPORARY_DIRECTORY_PATH__/rn_logs/toDelete.txt'
)
})
})
Loading