diff --git a/.eslintrc b/.eslintrc index 0cffcf1a..916571b7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -51,6 +51,7 @@ "no-restricted-syntax": 0, "no-prototype-builtins": 0, "no-void": 0, + "no-lonely-if": 0, "react/forbid-prop-types": 0, "react/jsx-curly-brace-presence": 0, "react/jsx-filename-extension": 0, diff --git a/package.json b/package.json index 9bd7f360..0dc5266c 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,9 @@ "tippy.js": "^2.5.2", "uncontrollable": "^6.0.0", "uuid": "^3.2.1", + "winston": "^3.0.0", + "winston-daily-rotate-file": "^3.2.3", + "winston-transport": "^4.2.0", "ws": "^5.1.1", "zxcvbn": "^4.4.2" }, diff --git a/src/components/ExportLogsBtn.js b/src/components/ExportLogsBtn.js index d509c266..389538ea 100644 --- a/src/components/ExportLogsBtn.js +++ b/src/components/ExportLogsBtn.js @@ -5,58 +5,37 @@ import fs from 'fs' import { webFrame, remote } from 'electron' import React, { Component } from 'react' import { translate } from 'react-i18next' -import { connect } from 'react-redux' import KeyHandler from 'react-key-handler' -import { createStructuredSelector, createSelector } from 'reselect' -import { accountsSelector, encodeAccountsModel } from 'reducers/accounts' -import { storeSelector as settingsSelector } from 'reducers/settings' +import { getCurrentLogFile } from 'helpers/resolveLogsDirectory' import Button from './base/Button' -const mapStateToProps = createStructuredSelector({ - accounts: createSelector(accountsSelector, encodeAccountsModel), - settings: settingsSelector, -}) - class ExportLogsBtn extends Component<{ t: *, - settings: ?*, - accounts: ?*, hookToShortcut?: boolean, }> { handleExportLogs = () => { - const { accounts, settings } = this.props - const logs = logger.exportLogs() + const srcLogFile = getCurrentLogFile() const resourceUsage = webFrame.getResourceUsage() - const report = { + logger.log('exportLogsMeta', { resourceUsage, - logs, - accounts, - settings, - date: new Date(), release: __APP_VERSION__, git_commit: __GIT_REVISION__, environment: __DEV__ ? 'development' : 'production', - } - console.log(report) // eslint-disable-line no-console - const reportJSON = JSON.stringify(report) + }) const path = remote.dialog.showSaveDialog({ title: 'Export logs', defaultPath: `ledgerlive-export-${moment().format( 'YYYY.MM.DD-HH.mm.ss', - )}-${__GIT_REVISION__ || 'unversionned'}.json`, + )}-${__GIT_REVISION__ || 'unversionned'}.log`, filters: [ { name: 'All Files', - extensions: ['json'], + extensions: ['log'], }, ], }) if (path) { - fs.writeFile(path, reportJSON, err => { - if (err) { - logger.error(err) - } - }) + fs.createReadStream(srcLogFile).pipe(fs.createWriteStream(path)) } } @@ -78,10 +57,4 @@ class ExportLogsBtn extends Component<{ } } -const WithAppData = connect(mapStateToProps)(ExportLogsBtn) -const WithoutAppData = ExportLogsBtn - -const ExportLogsBtnDispatcher = ({ withAppData, ...rest }: *) => - withAppData ? : - -export default translate()(ExportLogsBtnDispatcher) +export default translate()(ExportLogsBtn) diff --git a/src/components/Onboarding/index.js b/src/components/Onboarding/index.js index 9c6932bc..19d5790a 100644 --- a/src/components/Onboarding/index.js +++ b/src/components/Onboarding/index.js @@ -25,6 +25,7 @@ import { getCurrentDevice } from 'reducers/devices' import { unlock } from 'reducers/application' +import ExportLogsBtn from 'components/ExportLogsBtn' import Box from 'components/base/Box' import TriggerAppReady from '../TriggerAppReady' @@ -158,6 +159,8 @@ class Onboarding extends PureComponent { return ( + + {step.options.showBreadcrumb && } diff --git a/src/components/SettingsPage/sections/About.js b/src/components/SettingsPage/sections/About.js index 53042723..460d56eb 100644 --- a/src/components/SettingsPage/sections/About.js +++ b/src/components/SettingsPage/sections/About.js @@ -5,6 +5,7 @@ import { translate } from 'react-i18next' import type { T } from 'types/common' import TrackPage from 'analytics/TrackPage' import IconHelp from 'icons/Help' +import resolveLogsDirectory from 'helpers/resolveLogsDirectory' import ExportLogsBtn from 'components/ExportLogsBtn' import CleanButton from '../CleanButton' @@ -55,7 +56,10 @@ class SectionAbout extends PureComponent { > - + diff --git a/src/helpers/resolveLogsDirectory.js b/src/helpers/resolveLogsDirectory.js new file mode 100644 index 00000000..380fcb8e --- /dev/null +++ b/src/helpers/resolveLogsDirectory.js @@ -0,0 +1,30 @@ +// @flow + +import path from 'path' +import moment from 'moment' + +const resolveLogsDirectory = () => { + const { LEDGER_LOGS_DIRECTORY } = process.env + if (LEDGER_LOGS_DIRECTORY) return LEDGER_LOGS_DIRECTORY + const electron = require('electron') + return path.resolve((electron.app || electron.remote.app).getPath('userData'), 'logs') +} + +export default resolveLogsDirectory + +export const RotatingLogFileParameters = { + filename: 'application-%DATE%.log', + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '20m', + maxFiles: '14d', +} + +export const getCurrentLogFile = () => + path.resolve( + resolveLogsDirectory(), + RotatingLogFileParameters.filename.replace( + '%DATE%', + moment().format(RotatingLogFileParameters.datePattern), + ), + ) diff --git a/src/internals/index.js b/src/internals/index.js index 0db57625..1eb49a16 100644 --- a/src/internals/index.js +++ b/src/internals/index.js @@ -7,6 +7,8 @@ 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 = 'Internal' diff --git a/src/logger.js b/src/logger.js index 552fc071..a26beb6f 100644 --- a/src/logger.js +++ b/src/logger.js @@ -1,13 +1,8 @@ // @flow -/* eslint-disable no-console */ -/** - * IDEA: - * logger is an alternative to use for console.log that will be used for many purposes: - * - provide useful data for debugging during dev (idea is to have opt-in env var) - * - enabled in prod to provide useful data to debug when sending to Sentry - * - for analytics in the future - */ +import winston from 'winston' +import Transport from 'winston-transport' +import resolveLogsDirectory, { RotatingLogFileParameters } from 'helpers/resolveLogsDirectory' import { DEBUG_NETWORK, @@ -20,51 +15,83 @@ import { DEBUG_ANALYTICS, } from 'config/constants' -const logs = [] +require('winston-daily-rotate-file') -const MAX_LOG_LENGTH = 500 -const MAX_LOG_JSON_THRESHOLD = 2000 +let pname = '?' -const anonymousMode = !__DEV__ +const { format } = winston +const { combine, json, timestamp } = format + +const pinfo = format(info => { + info.pname = pname + return info +}) + +const transports = [ + new winston.transports.DailyRotateFile({ + dirname: resolveLogsDirectory(), + ...RotatingLogFileParameters, + }), +] -function addLog(type, ...args) { - logs.push({ type, date: new Date(), args }) - if (logs.length > MAX_LOG_LENGTH) { - logs.shift() +if (process.env.NODE_ENV !== 'production' || process.env.LOGS_IN_CONSOLE) { + let consoleT + if (typeof window === 'undefined') { + // on Node we want a concise logger + consoleT = new winston.transports.Console({ + format: format.simple(), + }) + } else { + // On Browser we want to preserve direct usage of console with the "expandable" objects + const SPLAT = Symbol.for('splat') + class CustomConsole extends Transport { + log(info, callback) { + setImmediate(() => { + this.emit('logged', info) + }) + const rest = info[SPLAT] + /* eslint-disable no-console */ + if (info.level === 'error') { + if (rest) { + console.error(info.message, ...rest) + } else { + console.error(info.message) + } + } else if (info.level === 'warn') { + if (rest) { + console.warn(info.message, ...rest) + } else { + console.warn(info.message) + } + } else { + if (rest) { + console.log(info.message, ...rest) + } else { + console.log(info.message) + } + } + /* eslint-enable no-console */ + callback() + } + } + consoleT = new CustomConsole() } + transports.push(consoleT) } +const logger = winston.createLogger({ + level: 'info', + format: combine(pinfo(), timestamp(), json()), + transports, +}) + +const anonymousMode = !__DEV__ + function anonymizeURL(url) { if (!anonymousMode) return url return url.replace(/\/addresses\/[^/]+/g, '/addresses/') } -const makeSerializableLog = (o: mixed) => { - if (typeof o === 'string') return o - if (typeof o === 'number') return o - if (typeof o === 'object' && o) { - try { - const json = JSON.stringify(o) - if (json.length < MAX_LOG_JSON_THRESHOLD) { - return o - } - // try to make a one level object on the same principle - const oneLevel = {} - Object.keys(o).forEach(key => { - // $FlowFixMe - oneLevel[key] = makeSerializableLog(o[key]) - }) - const json2 = JSON.stringify(oneLevel) - if (json2.length < MAX_LOG_JSON_THRESHOLD) { - return oneLevel - } - } catch (e) { - // This is not serializable so we will just stringify it - } - } - return String(o) -} - const logCmds = !__DEV__ || DEBUG_COMMANDS const logDb = !__DEV__ || DEBUG_DB const logRedux = !__DEV__ || DEBUG_ACTION @@ -75,42 +102,43 @@ const logNetwork = !__DEV__ || DEBUG_NETWORK const logAnalytics = !__DEV__ || DEBUG_ANALYTICS export default { + setProcessShortName: (processShortName: string) => { + pname = processShortName + }, + onCmd: (type: string, id: string, spentTime: number, data?: any) => { if (logCmds) { switch (type) { case 'cmd.START': - console.log(`CMD ${id}.send(`, data, ')') + logger.log('info', 'info', `CMD ${id}.send()`, { type, data }) break case 'cmd.NEXT': - console.log(`● CMD ${id}`, data) + logger.log('info', `● CMD ${id}`, { type, data }) break case 'cmd.COMPLETE': - console.log(`✔ CMD ${id} finished in ${spentTime.toFixed(0)}ms`) + logger.log('info', `✔ CMD ${id} finished in ${spentTime.toFixed(0)}ms`, { type }) break case 'cmd.ERROR': - console.warn(`✖ CMD ${id} error`, data) + logger.log('warn', `✖ CMD ${id} error`, { type, data }) break default: } } - addLog('cmd', type, id, spentTime, data) }, onDB: (way: 'read' | 'write' | 'clear', name: string) => { const msg = `📁 ${way} ${name}` if (logDb) { - console.log(msg) + logger.log('info', msg, { type: 'db' }) } - addLog('db', msg) }, // tracks Redux actions (NB not all actions are serializable) onReduxAction: (action: Object) => { if (logRedux) { - console.log(`⚛️ ${action.type}`, action) + logger.log('info', `⚛️ ${action.type}`, { type: 'action', action }) } - addLog('action', `⚛️ ${action.type}`, action) }, // tracks keyboard events @@ -120,31 +148,27 @@ export default { const displayEl = `${tagName.toLowerCase()}${classList.length ? ` ${classList.item(0)}` : ''}` const msg = `⇓ - active element ${displayEl}` if (logTabkey) { - console.log(msg) + logger.log('info', msg, { type: 'keydown' }) } - addLog('keydown', msg) }, websocket: (type: string, msg: *) => { if (logWS) { - console.log(`~ ${type}:`, msg) + logger.log('info', `~ ${type}:`, msg, { type: 'ws' }) } - addLog('ws', `~ ${type}`, msg) }, libcore: (level: string, msg: string) => { if (logLibcore) { - console.log(`🛠 ${level}: ${msg}`) + logger.log('info', `🛠 ${level}: ${msg}`, { type: 'libcore' }) } - addLog('action', `🛠 ${level}: ${msg}`) }, network: ({ method, url }: { method: string, url: string }) => { const log = `➡📡 ${method} ${anonymizeURL(url)}` if (logNetwork) { - console.log(log) + logger.log('info', log, { type: 'network' }) } - addLog('network', log) }, networkSucceed: ({ @@ -162,9 +186,8 @@ export default { url, )} – finished in ${responseTime.toFixed(0)}ms` if (logNetwork) { - console.log(log) + logger.log('info', log, { type: 'network-response' }) } - addLog('network-response', log) }, networkError: ({ @@ -184,9 +207,8 @@ export default { url, )} – ${error} – failed after ${responseTime.toFixed(0)}ms` if (logNetwork) { - console.log(log) + logger.log('info', log, { type: 'network-error', status, method }) } - addLog('network-error', log) }, networkDown: ({ @@ -202,59 +224,50 @@ export default { 0, )}ms` if (logNetwork) { - console.log(log) + logger.log('info', log, { type: 'network-down' }) } - addLog('network-down', log) }, analyticsStart: (id: string) => { if (logAnalytics) { - console.log(`△ start() with user id ${id}`) + logger.log('info', `△ start() with user id ${id}`, { type: 'anaytics-start', id }) } - addLog('anaytics-start', id) }, analyticsStop: () => { if (logAnalytics) { - console.log(`△ stop()`) + logger.log('info', `△ stop()`, { type: 'anaytics-stop' }) } - addLog('anaytics-stop') }, analyticsTrack: (event: string, properties: ?Object) => { if (logAnalytics) { - console.log(`△ track ${event}`, properties) + logger.log('info', `△ track ${event}`, { type: 'anaytics-track', properties }) } - addLog('anaytics-track', `${event}`) }, analyticsPage: (category: string, name: ?string, properties: ?Object) => { if (logAnalytics) { - console.log(`△ page ${category} ${name || ''}`, properties) + logger.log('info', `△ page ${category} ${name || ''}`, { type: 'anaytics-page', properties }) } - addLog('anaytics-page', `${category} ${name || ''}`) }, // General functions in case the hooks don't apply log: (...args: any) => { - console.log(...args) - addLog('log', ...args) + logger.log('info', ...args) }, warn: (...args: any) => { - console.warn(...args) - addLog('warn', ...args) + logger.log('warn', ...args) }, error: (...args: any) => { - console.error(...args) - addLog('error', ...args) + logger.log('error', ...args) }, critical: (error: Error) => { - addLog('critical', error) - console.error(error) + logger.log('error', error) if (!process.env.STORYBOOK_ENV) { try { if (typeof window !== 'undefined') { @@ -263,15 +276,8 @@ export default { require('sentry/node').captureException(error) } } catch (e) { - console.warn("Can't send to sentry", error, e) + logger.log('warn', "Can't send to sentry", error, e) } } }, - - exportLogs: (): Array<{ type: string, date: Date, args: Array }> => - logs.map(({ type, date, args }) => ({ - type, - date, - args: args.map(makeSerializableLog), - })), } diff --git a/src/main/bridge.js b/src/main/bridge.js index 5ed618b2..88cfd4f3 100644 --- a/src/main/bridge.js +++ b/src/main/bridge.js @@ -9,14 +9,18 @@ import path from 'path' import logger from 'logger' import sentry from 'sentry/node' import user from 'helpers/user' +import resolveLogsDirectory from 'helpers/resolveLogsDirectory' import setupAutoUpdater, { quitAndInstall } from './autoUpdate' 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() let internalProcess @@ -45,6 +49,7 @@ const bootInternalProcess = () => { internalProcess = fork(forkBundlePath, { env: { ...process.env, + LEDGER_LOGS_DIRECTORY, LEDGER_LIVE_SQLITE_PATH, INITIAL_SENTRY_ENABLED: sentryEnabled, SENTRY_USER_ID: userId, diff --git a/src/renderer/init.js b/src/renderer/init.js index 951590c7..3b63d849 100644 --- a/src/renderer/init.js +++ b/src/renderer/init.js @@ -31,6 +31,8 @@ import AppError from 'components/AppError' import 'styles/global' +logger.setProcessShortName('renderer') + const rootNode = document.getElementById('app') const TAB_KEY = 9 diff --git a/static/i18n/en/app.yml b/static/i18n/en/app.yml index ce88531b..57f6db72 100644 --- a/static/i18n/en/app.yml +++ b/static/i18n/en/app.yml @@ -333,8 +333,6 @@ settings: # Always ensure descriptions carry full stops (.) changePassword: Change password sync: Synchronize accounts syncDesc: Resynchronize your accounts with the network. - export: Export logs - exportDesc: Exporting Ledger Live logs may be necessary for troubleshooting purposes. softResetTitle: Clear cache softResetDesc: Clear the Ledger Live cache to force resynchronization with the blockchain. softReset: Clear @@ -371,7 +369,7 @@ settings: # Always ensure descriptions carry full stops (.) desc: The account will no longer be included in your portfolio. This operation does not affect your assets. Accounts can always be re-added. exportLogs: title: Export logs - desc: Exporting Ledger Live logs may be necessary for troubleshooting purposes. + desc: 'Exporting Ledger Live logs may be necessary for troubleshooting purposes.' btn: Export password: warning_0: Very weak diff --git a/yarn.lock b/yarn.lock index e85f98a2..f1f62680 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4438,16 +4438,16 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" +color-convert@^0.5.0, color-convert@~0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd" + color-convert@^1.3.0, color-convert@^1.9.0, color-convert@^1.9.1: version "1.9.2" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.2.tgz#49881b8fba67df12a96bdf3f56c0aab9e7913147" dependencies: color-name "1.1.1" -color-convert@~0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd" - color-name@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689" @@ -4469,6 +4469,13 @@ color-string@^1.5.2: color-name "^1.0.0" simple-swizzle "^0.2.2" +color@0.8.x: + version "0.8.0" + resolved "https://registry.yarnpkg.com/color/-/color-0.8.0.tgz#890c07c3fd4e649537638911cf691e5458b6fca5" + dependencies: + color-convert "^0.5.0" + color-string "^0.3.0" + color@^0.11.0: version "0.11.4" resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764" @@ -4492,11 +4499,15 @@ colormin@^1.0.5: css-color-names "0.0.4" has "^1.0.1" +colornames@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/colornames/-/colornames-0.0.2.tgz#d811fd6c84f59029499a8ac4436202935b92be31" + colors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" -colors@^1.1.2: +colors@^1.1.2, colors@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.0.tgz#5f20c9fef6945cb1134260aab33bfbdc8295e04e" @@ -4504,6 +4515,13 @@ colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" +colorspace@1.0.x: + version "1.0.1" + resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.0.1.tgz#c99c796ed31128b9876a52e1ee5ee03a4a719749" + dependencies: + color "0.8.x" + text-hex "0.0.x" + columnify@~1.5.4: version "1.5.4" resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" @@ -5011,6 +5029,10 @@ currently-unhandled@^0.4.1: dependencies: array-find-index "^1.0.1" +cycle@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" + cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" @@ -5453,6 +5475,14 @@ dezalgo@^1.0.0, dezalgo@~1.0.3: asap "^2.0.0" wrappy "1" +diagnostics@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/diagnostics/-/diagnostics-1.1.0.tgz#e1090900b49523e8527be20f081275205f2ae36a" + dependencies: + colorspace "1.0.x" + enabled "1.0.x" + kuler "0.0.x" + diff@^3.2.0, diff@^3.3.1, diff@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -5926,6 +5956,12 @@ emotion@^9.1.2: babel-plugin-emotion "^9.2.4" create-emotion "^9.2.4" +enabled@1.0.x: + version "1.0.2" + resolved "https://registry.yarnpkg.com/enabled/-/enabled-1.0.2.tgz#965f6513d2c2d1c5f4652b64a2e3396467fc2f93" + dependencies: + env-variable "0.0.x" + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -5967,6 +6003,10 @@ env-paths@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0" +env-variable@0.0.x: + version "0.0.4" + resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.4.tgz#0d6280cf507d84242befe35a512b5ae4be77c54e" + envinfo@^5.7.0: version "5.10.0" resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-5.10.0.tgz#503a9774ae15b93ea68bdfae2ccd6306624ea6df" @@ -6572,6 +6612,10 @@ fast-memoize@^2.2.7: version "2.5.1" resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.5.1.tgz#c3519241e80552ce395e1a32dcdde8d1fd680f5d" +fast-safe-stringify@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.4.tgz#4fe828718aa61dbcf9119c3c24e79cc4dea973b2" + fastparse@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" @@ -6612,6 +6656,10 @@ fd-slicer@~1.0.1: dependencies: pend "~1.2.0" +fecha@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd" + figgy-pudding@^3.0.0, figgy-pudding@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.1.0.tgz#a77ed2284175976c424b390b298569e9df86dd1e" @@ -6643,6 +6691,12 @@ file-loader@^1.1.11: loader-utils "^1.0.2" schema-utils "^0.4.5" +file-stream-rotator@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/file-stream-rotator/-/file-stream-rotator-0.2.1.tgz#0d6fea1a9a7aba25a87cfd31b6e269e44e8f0af2" + dependencies: + moment "^2.11.2" + filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -8915,6 +8969,12 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" +kuler@0.0.x: + version "0.0.0" + resolved "https://registry.yarnpkg.com/kuler/-/kuler-0.0.0.tgz#b66bb46b934e550f59d818848e0abba4f7f5553c" + dependencies: + colornames "0.0.2" + latest-version@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" @@ -9235,6 +9295,16 @@ log-update@^1.0.2: ansi-escapes "^1.0.0" cli-cursor "^1.0.2" +logform@^1.6.0, logform@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/logform/-/logform-1.9.1.tgz#58b29d7b11c332456d7a217e17b48a13ad69d60a" + dependencies: + colors "^1.2.1" + fast-safe-stringify "^2.0.4" + fecha "^2.3.3" + ms "^2.1.1" + triple-beam "^1.2.0" + loglevel@^1.4.1: version "1.6.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" @@ -9702,7 +9772,7 @@ mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdi dependencies: minimist "0.0.8" -moment@^2.21.0, moment@^2.22.2: +moment@^2.11.2, moment@^2.21.0, moment@^2.22.2: version "2.22.2" resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" @@ -9721,7 +9791,7 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@2.1.1, ms@^2.0.0: +ms@2.1.1, ms@^2.0.0, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" @@ -10364,6 +10434,10 @@ once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0, once@~1.4.0: dependencies: wrappy "1" +one-time@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e" + onetime@^1.0.0: version "1.1.0" resolved "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" @@ -13190,7 +13264,7 @@ stable@~0.1.6: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" -stack-trace@0.0.10: +stack-trace@0.0.10, stack-trace@0.0.x: version "0.0.10" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" @@ -13631,6 +13705,10 @@ test-exclude@^4.2.1: read-pkg-up "^1.0.1" require-main-filename "^1.0.1" +text-hex@0.0.x: + version "0.0.0" + resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-0.0.0.tgz#578fbc85a6a92636e42dd17b41d0218cce9eb2b3" + text-table@0.2.0, text-table@^0.2.0, text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -13798,6 +13876,10 @@ trim@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" +triple-beam@^1.2.0, triple-beam@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" + trough@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.2.tgz#7f1663ec55c480139e2de5e486c6aef6cc24a535" @@ -14657,6 +14739,45 @@ window-size@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" +winston-compat@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/winston-compat/-/winston-compat-0.1.4.tgz#599b4ce807ffe728713ecc25ede3f6b89425b739" + dependencies: + cycle "~1.0.3" + logform "^1.6.0" + triple-beam "^1.2.0" + +winston-daily-rotate-file@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/winston-daily-rotate-file/-/winston-daily-rotate-file-3.2.3.tgz#9f80e7a421ab32b073c1217bae62e762001197d6" + dependencies: + file-stream-rotator "^0.2.1" + semver "^5.5.0" + triple-beam "^1.3.0" + winston-compat "^0.1.4" + winston-transport "^4.2.0" + +winston-transport@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.2.0.tgz#a20be89edf2ea2ca39ba25f3e50344d73e6520e5" + dependencies: + readable-stream "^2.3.6" + triple-beam "^1.2.0" + +winston@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.0.0.tgz#1f0b24a96586798bcf0cd149fb07ed47cb01a1b2" + dependencies: + async "^2.6.0" + diagnostics "^1.0.1" + is-stream "^1.1.0" + logform "^1.9.0" + one-time "0.0.4" + readable-stream "^2.3.6" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.2.0" + wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"