From 6abee8c2aabe4d58c94700a3b0563cd3be751e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Wed, 11 Jul 2018 13:20:04 +0200 Subject: [PATCH] Better logger system, fix race condition, smaller log exports --- src/components/ExportLogsBtn.js | 31 +++++++--------- src/helpers/pname.js | 12 +++++++ src/helpers/resolveLogsDirectory.js | 16 --------- src/internals/index.js | 2 -- src/logger/logger-storybook.js | 1 - src/logger/logger.js | 56 +++++++++++++++++++++-------- src/main/bridge.js | 3 +- src/renderer/init.js | 2 -- src/sentry/install.js | 4 +-- 9 files changed, 70 insertions(+), 57 deletions(-) create mode 100644 src/helpers/pname.js diff --git a/src/components/ExportLogsBtn.js b/src/components/ExportLogsBtn.js index c63d5089..eb0ddd34 100644 --- a/src/components/ExportLogsBtn.js +++ b/src/components/ExportLogsBtn.js @@ -6,22 +6,17 @@ import { webFrame, remote } from 'electron' import React, { Component } from 'react' import { translate } from 'react-i18next' import KeyHandler from 'react-key-handler' -import { getCurrentLogFile } from 'helpers/resolveLogsDirectory' import Button from './base/Button' -function copyFile(source, target) { - const rd = fs.createReadStream(source) - const wr = fs.createWriteStream(target) +function writeToFile(file, data) { return new Promise((resolve, reject) => { - rd.on('error', reject) - wr.on('error', reject) - wr.on('finish', resolve) - rd.pipe(wr) - }).catch(error => { - // $FlowFixMe - rd.destroy() - wr.end() - throw error + fs.writeFile(file, data, error => { + if (error) { + reject(error) + } else { + resolve() + } + }) }) } @@ -30,9 +25,7 @@ class ExportLogsBtn extends Component<{ hookToShortcut?: boolean, }> { export = async () => { - const srcLogFile = await getCurrentLogFile() const resourceUsage = webFrame.getResourceUsage() - const ext = srcLogFile.match(/[.]log[.]gz$/) ? 'log.gz' : 'log' logger.log('exportLogsMeta', { resourceUsage, release: __APP_VERSION__, @@ -44,16 +37,18 @@ class ExportLogsBtn extends Component<{ title: 'Export logs', defaultPath: `ledgerlive-export-${moment().format( 'YYYY.MM.DD-HH.mm.ss', - )}-${__GIT_REVISION__ || 'unversionned'}.${ext}`, + )}-${__GIT_REVISION__ || 'unversionned'}.json`, filters: [ { name: 'All Files', - extensions: [ext], + extensions: ['json'], }, ], }) if (path) { - await copyFile(srcLogFile, path) + const logs = await logger.queryAllLogs() + const json = JSON.stringify(logs) + await writeToFile(path, json) } } diff --git a/src/helpers/pname.js b/src/helpers/pname.js new file mode 100644 index 00000000..231651b3 --- /dev/null +++ b/src/helpers/pname.js @@ -0,0 +1,12 @@ +// @flow + +// Infer a "pname" aka short id version of process name + +const pname = + typeof window === 'undefined' + ? process.env.IS_INTERNAL_PROCESS + ? 'internal' + : 'main' + : 'renderer' + +export default pname diff --git a/src/helpers/resolveLogsDirectory.js b/src/helpers/resolveLogsDirectory.js index e6fedb00..cbfd8afa 100644 --- a/src/helpers/resolveLogsDirectory.js +++ b/src/helpers/resolveLogsDirectory.js @@ -1,6 +1,5 @@ // @flow -import fs from 'fs' import path from 'path' const resolveLogsDirectory = () => { @@ -11,18 +10,3 @@ const resolveLogsDirectory = () => { } export default resolveLogsDirectory - -export const getCurrentLogFile = () => - new Promise((resolve, reject) => { - const dir = resolveLogsDirectory() - fs.readdir(dir, (err, files) => { - if (err) { - reject(err) - } else { - // last file is always the most up to date log. file will rotate. - const last = files[files.length - 1] - if (!last) reject(new Error('no logs')) - else resolve(path.resolve(dir, last)) - } - }) - }) diff --git a/src/internals/index.js b/src/internals/index.js index cc78f090..3d72e503 100644 --- a/src/internals/index.js +++ b/src/internals/index.js @@ -7,8 +7,6 @@ import sentry from 'sentry/node' import { EXPERIMENTAL_HTTP_ON_RENDERER } from 'config/constants' import { serializeError } from 'helpers/errors' -logger.setProcessShortName('internal') - require('../env') process.title = 'Ledger Live Internal' diff --git a/src/logger/logger-storybook.js b/src/logger/logger-storybook.js index 1fcc6163..eee5c550 100644 --- a/src/logger/logger-storybook.js +++ b/src/logger/logger-storybook.js @@ -1,7 +1,6 @@ const noop = () => {} module.exports = { - setProcessShortName: noop, onCmd: noop, onDB: noop, onReduxAction: noop, diff --git a/src/logger/logger.js b/src/logger/logger.js index a26119c5..7f001def 100644 --- a/src/logger/logger.js +++ b/src/logger/logger.js @@ -4,6 +4,7 @@ import winston from 'winston' import Transport from 'winston-transport' import resolveLogsDirectory from 'helpers/resolveLogsDirectory' import anonymizer from 'helpers/anonymizer' +import pname from 'helpers/pname' import { DEBUG_DEVICE, @@ -19,8 +20,6 @@ import { require('winston-daily-rotate-file') -let pname = '?' - const { format } = winston const { combine, json, timestamp } = format @@ -29,16 +28,49 @@ const pinfo = format(info => { return info }) -const transports = [ - new winston.transports.DailyRotateFile({ +function createDailyRotateFile(processName) { + return new winston.transports.DailyRotateFile({ dirname: resolveLogsDirectory(), + json: true, zippedArchive: true, - filename: 'application-%DATE%.log', + filename: `ledger-live-${processName}-%DATE%.log`, datePattern: 'YYYY-MM-DD', - maxSize: '20m', + maxSize: '10m', maxFiles: '14d', - }), -] + }) +} + +const transports = [createDailyRotateFile(pname)] + +const queryLogs = (processName: string) => + new Promise((resolve, reject) => { + const dailyRotateFile = createDailyRotateFile(processName) + const options = { + from: new Date() - 60 * 60 * 1000, + until: new Date(), + limit: 100, + start: 0, + order: 'desc', + } + dailyRotateFile.query(options, (err, result) => { + if (err) { + reject(err) + return + } + resolve(result) + }) + }) + +const queryAllLogs = async () => { + const internal = await queryLogs('internal') + const main = await queryLogs('main') + const renderer = await queryLogs('renderer') + const all = internal + .concat(main) + .concat(renderer) + .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)) + return all +} if (process.env.NODE_ENV !== 'production' || process.env.DEV_TOOLS) { let consoleT @@ -125,12 +157,6 @@ const blacklistTooVerboseCommandResponse = [ ] export default { - setProcessShortName: (processShortName: string) => { - pname = processShortName - }, - - getProcessShortName: () => pname, - onCmd: (type: string, id: string, spentTime: number, data?: any) => { if (logCmds) { switch (type) { @@ -352,4 +378,6 @@ export default { } } }, + + queryAllLogs, } diff --git a/src/main/bridge.js b/src/main/bridge.js index b36550be..8ec0ab74 100644 --- a/src/main/bridge.js +++ b/src/main/bridge.js @@ -17,8 +17,6 @@ import { setInternalProcessPID } from './terminator' import { getMainWindow } from './app' -logger.setProcessShortName('main') - // sqlite files will be located in the app local data folder const LEDGER_LIVE_SQLITE_PATH = path.resolve(app.getPath('userData'), 'sqlite') const LEDGER_LOGS_DIRECTORY = process.env.LEDGER_LOGS_DIRECTORY || resolveLogsDirectory() @@ -51,6 +49,7 @@ const bootInternalProcess = () => { internalProcess = fork(forkBundlePath, { env: { ...process.env, + IS_INTERNAL_PROCESS: 1, LEDGER_LOGS_DIRECTORY, LEDGER_CONFIG_DIRECTORY, LEDGER_LIVE_SQLITE_PATH, diff --git a/src/renderer/init.js b/src/renderer/init.js index 0e7cf4b2..f63ca39f 100644 --- a/src/renderer/init.js +++ b/src/renderer/init.js @@ -32,8 +32,6 @@ import AppError from 'components/AppError' import 'styles/global' -logger.setProcessShortName('renderer') - const rootNode = document.getElementById('app') const TAB_KEY = 9 diff --git a/src/sentry/install.js b/src/sentry/install.js index 3fba01aa..5b78ddf8 100644 --- a/src/sentry/install.js +++ b/src/sentry/install.js @@ -1,5 +1,5 @@ // @flow -import logger from 'logger' +import pname from 'helpers/pname' import anonymizer from 'helpers/anonymizer' /* eslint-disable no-continue */ @@ -24,7 +24,7 @@ export default (Raven: any, shouldSendCallback: () => boolean, userId: string) = sentry: true, }, extra: { - process: logger.getProcessShortName(), + process: pname, }, dataCallback: (data: mixed) => { // We are mutating the data to anonymize everything.