From f55f43d19a23845d66eb01616f5f817be1b3d654 Mon Sep 17 00:00:00 2001 From: Karissa McKelvey <633012+okdistribute@users.noreply.github.com> Date: Tue, 23 Jun 2020 15:04:57 -0700 Subject: [PATCH] Better logging, and log files written to disk (#361) remove unused bugsnag errors --- .eslintrc | 1 + .gitignore | 4 +- README.md | 18 +- index.js | 52 +-- messages/main/en.json | 2 + package-lock.json | 295 +++++++++++++++++- package.json | 6 +- src/background/index.js | 27 +- src/background/mapeo-ipc.js | 67 ++-- src/background/mapeo.js | 39 ++- src/background/server.js | 2 +- src/background/tile-importer.js | 7 +- src/logger.js | 169 +++++++++- src/main/i18n.js | 7 +- src/main/ipc.js | 4 +- src/main/menu.js | 35 ++- src/main/user-config.js | 4 +- src/renderer/app.js | 17 +- .../components/MapEditor/ExportButton.js | 2 +- .../components/MapFilter/DataExportDialog.js | 5 +- .../components/MapFilter/MapExportDialog.js | 4 +- .../components/MapFilter/MapFilter.js | 6 +- .../components/SyncView/SyncAppBar.js | 6 +- src/renderer/components/SyncView/index.js | 8 +- src/renderer/create-zip.js | 10 +- src/renderer/new-api.js | 12 +- src/store.js | 8 +- 27 files changed, 673 insertions(+), 144 deletions(-) diff --git a/.eslintrc b/.eslintrc index 02767f0cb..a4d125283 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,6 +15,7 @@ "browser": true }, "rules": { + "no-proto": 0, "react/prop-types": 0, "react/display-name": 0, "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks diff --git a/.gitignore b/.gitignore index 856e7fa9c..4e476ba4e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ examples/.dat static/build.js test/lib/test-data bin/test-data -/translations +translations +storybook-static +updates diff --git a/README.md b/README.md index 52ef80ad7..500a9bf81 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,17 @@ Then, in another terminal, run the app in development mode: npm run dev ``` -## Testing +Running `npm run dev` will run the background process in an electron window +that can be stepped through similarly to the front-end code. + +To see log messages in real-time while in debug mode, run `tail` in another +terminal window: + +```sh +tail -f USERDATA/Mapeo/logs/$DATE.debug.log +``` + +### Debbugging Data is stored in `USERDATA/Mapeo` on your machine, which will be different depending on your operating system. @@ -54,6 +64,12 @@ To simulate a reinstall, remove this `Mapeo` directory. To only delete data and not presets or tiles, delete the `Mapeo/kappa.db` directory. +In production mode, `info` and `error` messages are written to +`USERDATA/Mapeo/logs/$DATE.log` and kept for 1 year. + +In debug mode and development mode (via `npm run dev` or in the Help menu), +Mapeo will verbosely write all messages for 1 day. + ### Run a mock device diff --git a/index.js b/index.js index 49360425d..9dad0063c 100755 --- a/index.js +++ b/index.js @@ -16,12 +16,13 @@ const app = electron.app const BrowserWindow = electron.BrowserWindow const logger = require('./src/logger') -const miscellaneousIpc = require('./src/main/ipc') +const electronIpc = require('./src/main/ipc') const createMenu = require('./src/main/menu') const windowStateKeeper = require('./src/main/window-state') // HACK: enable GPU graphics acceleration on some older laptops app.commandLine.appendSwitch('ignore-gpu-blacklist', 'true') +app.commandLine.appendSwitch('ignore-certificate-errors') // Setup some handy dev tools shortcuts (only activates in dev mode) // See https://github.com/sindresorhus/electron-debug @@ -57,6 +58,14 @@ if (!gotTheLock) { // https://github.com/atom/electron/blob/master/docs/api/app.md#appgetpathname var userDataPath = app.getPath('userData') +if (!logger.configured) { + logger.configure({ + label: 'main', + userDataPath, + isDev + }) +} + var argv = minimist(process.argv.slice(2), { default: { port: 5000, @@ -74,7 +83,7 @@ var argv = minimist(process.argv.slice(2), { var _socketName rabbit.findOpenSocket('mapeo').then((socketName) => { - logger.log('got socket', socketName) + logger.debug('got socket', socketName) _socketName = socketName if (argv.headless) startSequence() else app.once('ready', openWindow) @@ -89,7 +98,7 @@ app.on('window-all-closed', function () { function openWindow () { ipc.on('error', function (err) { - logger.error(err) + logger.error('ipc', err) electron.dialog.showErrorBox('Error', err) }) ipc.connect(_socketName) @@ -108,8 +117,8 @@ function openWindow () { } = require('electron-devtools-installer') } catch (e) {} installExtension(REACT_DEVELOPER_TOOLS) - .then(name => logger.log(`Added Extension: ${name}`)) - .catch(err => logger.log('An error occurred: ', err)) + .then(name => logger.debug(`Added Extension: ${name}`)) + .catch(err => logger.error('Failed to add extension', err)) } else { createBackgroundProcess(_socketName) } @@ -131,7 +140,7 @@ function openWindow () { function startupMsg (txt) { return function (done) { - logger.log('[STARTUP] ' + txt) + logger.debug('[STARTUP] ' + txt) done() } } @@ -151,7 +160,7 @@ function startSequence () { ], function (err) { if (err) logger.error('STARTUP FAILED', err) - else logger.log('STARTUP success!') + else logger.debug('STARTUP success!') } ) } @@ -170,10 +179,9 @@ function initDirectories (done) { } function createServers (done) { - // TODO: rename/refactor - miscellaneousIpc(win) + electronIpc(win) - logger.log('initializing mapeo', userDataPath, argv.port) + logger.info('initializing mapeo', userDataPath, argv.port) var opts = { userDataPath, datadir: argv.datadir, @@ -183,9 +191,8 @@ function createServers (done) { ipc.send('listen', opts, function (err, port) { if (err) throw new Error('fatal: could not get port', err) - logger.log('listen port got back', port) global.osmServerHost = '127.0.0.1:' + port - logger.log(global.osmServerHost) + logger.info(global.osmServerHost) done() }) } @@ -243,6 +250,7 @@ function createWindow (socketName) { // Create a hidden background window function createBgWindow (socketName) { + logger.debug('loading electron background window') var win = new BrowserWindow({ x: 0, y: 0, @@ -253,17 +261,18 @@ function createBgWindow (socketName) { nodeIntegration: true } }) - console.log('loading bg window') var BG = 'file://' + path.join(__dirname, './src/background/index.html') win.loadURL(BG) win.webContents.on('did-finish-load', () => { if (argv.debug) bg.webContents.openDevTools() - win.webContents.send('set-socket', { - name: socketName + win.webContents.send('configure', { + socketName, + userDataPath, + isDev }) }) win.on('closed', () => { - console.log('background window closed') + logger.info('Background window closed') app.quit() }) return win @@ -284,15 +293,16 @@ function createSplashWindow () { } function createBackgroundProcess (socketName) { - console.log('creating background process') + logger.debug('creating background process') serverProcess = fork(path.join(__dirname, 'src', 'background', 'index.js'), [ '--subprocess', app.getVersion(), - socketName + socketName, + userDataPath ]) serverProcess.on('message', msg => { - console.log(msg) + logger.debug(msg) }) } @@ -322,9 +332,9 @@ function beforeQuit (e) { // 'close' event will gracefully close databases and wait for pending sync // TODO: Show the user that a sync is pending finishing - console.log('ipc.send close') + logger.debug('ipc.send close') ipc.send('close', null, () => { - console.log('closed') + logger.info('IPC closed') clearTimeout(closingTimeoutId) if (serverProcess) serverProcess.kill() try { closingWin.close() } catch (e) {} diff --git a/messages/main/en.json b/messages/main/en.json index 0d7f34bd4..ab44a15da 100644 --- a/messages/main/en.json +++ b/messages/main/en.json @@ -49,6 +49,8 @@ "menu-copy": "Copy", "menu-paste": "Paste", "menu-selectall": "Select all", + "menu-debugging": "Debug", + "menu-report": "Report issue...", "save-db-dialog": "Create a new database to syncronize", "open-db-dialog": "Select a database to syncronize", "feedback-contribute-button": "Feedback & Contribute", diff --git a/package-lock.json b/package-lock.json index e23df6336..b0c780a9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1173,6 +1173,69 @@ "to-fast-properties": "^2.0.0" } }, + "@bugsnag/browser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@bugsnag/browser/-/browser-7.1.1.tgz", + "integrity": "sha512-9LjNsSVhnHizEtixPa8XnB1LYWAfdSf7spKwVW8UBBt1yfa9h/q0AhU6CrxEPi/uehkQaHx5k/2ZvaMP5G5SAA==", + "requires": { + "@bugsnag/core": "^7.1.1" + } + }, + "@bugsnag/core": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@bugsnag/core/-/core-7.1.1.tgz", + "integrity": "sha512-PWj2JQJIIbv4lP1YBR88hA9fS3UbGLwn+1ZxxUK5xxcUbTpViMIaoMGdQflfxHh7xfH6YmtwgA5mg2zDZGHCBw==", + "requires": { + "@bugsnag/cuid": "^3.0.0", + "@bugsnag/safe-json-stringify": "^5.0.0", + "error-stack-parser": "^2.0.3", + "iserror": "0.0.2", + "stack-generator": "^2.0.3" + } + }, + "@bugsnag/cuid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@bugsnag/cuid/-/cuid-3.0.0.tgz", + "integrity": "sha512-LOt8aaBI+KvOQGneBtpuCz3YqzyEAehd1f3nC5yr9TIYW1+IzYKa2xWS4EiMz5pPOnRPHkyyS5t/wmSmN51Gjg==" + }, + "@bugsnag/js": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@bugsnag/js/-/js-7.1.1.tgz", + "integrity": "sha512-pa7d3kRt398hvrhjKvEzMBKE+m1JOJpwW7c8+Btu6HrjCxBcLj7SeSzzN2mbDmT/DfPU4hjeZ+oXGTWVWjUkgQ==", + "requires": { + "@bugsnag/browser": "^7.1.1", + "@bugsnag/node": "^7.1.1" + } + }, + "@bugsnag/node": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@bugsnag/node/-/node-7.1.1.tgz", + "integrity": "sha512-1ciecltpLBfr1kPTrSoHt8umPDZXxHAWdgVE1jx4QyTl1+e5WIpMvp5cs36jisIR+dbOKBB5Y3hgv6VOX5u50A==", + "requires": { + "@bugsnag/core": "^7.1.1", + "byline": "^5.0.0", + "error-stack-parser": "^2.0.2", + "iserror": "^0.0.2", + "pump": "^3.0.0", + "stack-generator": "^2.0.3" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "@bugsnag/safe-json-stringify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@bugsnag/safe-json-stringify/-/safe-json-stringify-5.0.0.tgz", + "integrity": "sha512-EdGnA7n2UmX9Z0e2tIU0biKz8tCgtjbY69KoyH3yiMzm7Q9sriIlN9bZjqhTOKzM0GdLSGrkyKWif08xWKyucA==" + }, "@chromaui/localtunnel": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/@chromaui/localtunnel/-/localtunnel-1.10.1.tgz", @@ -4992,6 +5055,11 @@ "readable-stream": "^2.1.4" } }, + "byline": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", + "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=" + }, "bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", @@ -5707,12 +5775,48 @@ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true }, + "colornames": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", + "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" + }, "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "optional": true + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, + "colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "requires": { + "color": "3.0.x", + "text-hex": "1.0.x" + }, + "dependencies": { + "color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + } + } }, "combined-stream": { "version": "1.0.8", @@ -5873,9 +5977,9 @@ } }, "conf": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/conf/-/conf-6.2.0.tgz", - "integrity": "sha512-fvl40R6YemHrFsNiyP7TD0tzOe3pQD2dfT2s20WvCaq57A1oV+RImbhn2Y4sQGDz1lB0wNSb7dPcPIvQB69YNA==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/conf/-/conf-6.2.4.tgz", + "integrity": "sha512-GjgyPRLo1qK1LR9RWAdUagqo+DP18f5HWCFk4va7GS+wpxQTOzfuKTwKOvGW2c01/YXNicAyyoyuSddmdkBzZQ==", "requires": { "ajv": "^6.10.2", "debounce-fn": "^3.0.1", @@ -7342,6 +7446,16 @@ "resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz", "integrity": "sha1-PvqHMj67hj5mls67AILUj/PW96E=" }, + "diagnostics": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", + "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", + "requires": { + "colorspace": "1.1.x", + "enabled": "1.0.x", + "kuler": "1.0.x" + } + }, "diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -8224,6 +8338,14 @@ "hoist-non-react-statics": "^3.3.0" } }, + "enabled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", + "requires": { + "env-variable": "0.0.x" + } + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -8300,6 +8422,11 @@ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==" }, + "env-variable": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz", + "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==" + }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -8333,6 +8460,14 @@ } } }, + "error-stack-parser": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "requires": { + "stackframe": "^1.1.1" + } + }, "es-abstract": { "version": "1.17.4", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", @@ -9642,6 +9777,11 @@ "array-back": "^1.0.4" } }, + "fecha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" + }, "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", @@ -9702,6 +9842,14 @@ "stream-source": "0.3" } }, + "file-stream-rotator": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.5.7.tgz", + "integrity": "sha512-VYb3HZ/GiAGUCrfeakO8Mp54YGswNUHvL7P09WQcXAJNSj3iQ5QraYSp3cIn1MUyw6uzfgN/EFOarCNa4JvUHQ==", + "requires": { + "moment": "^2.11.2" + } + }, "file-system-cache": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/file-system-cache/-/file-system-cache-1.0.5.tgz", @@ -13164,6 +13312,11 @@ "integrity": "sha512-pEutbN134CzcjlLS1myKX/uxNjwU5eBVSprvkpv3+3dqhBHUZLIWJQowC40w5c0Zf19vBY8mrZl88y5J4RAPbQ==", "dev": true }, + "iserror": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/iserror/-/iserror-0.0.2.tgz", + "integrity": "sha1-vVNFH+L2aLnyQCwZZnh6qix8C/U=" + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -13944,6 +14097,14 @@ "graceful-fs": "^4.1.11" } }, + "kuler": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", + "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", + "requires": { + "colornames": "^1.1.1" + } + }, "ky": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/ky/-/ky-0.14.0.tgz", @@ -14474,6 +14635,25 @@ "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=", "dev": true }, + "logform": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", + "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", + "requires": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.3.0" + }, + "dependencies": { + "fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + } + } + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -15373,8 +15553,7 @@ "moment": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", - "dev": true + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, "move-concurrently": { "version": "1.0.1", @@ -20034,6 +20213,11 @@ } } }, + "object-hash": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.3.tgz", + "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==" + }, "object-inspect": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", @@ -20189,6 +20373,11 @@ "wrappy": "1" } }, + "one-time": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", + "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" + }, "onetime": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", @@ -24601,6 +24790,24 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", "dev": true }, + "stack-generator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.5.tgz", + "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", + "requires": { + "stackframe": "^1.1.1" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "stackframe": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", + "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==" + }, "standard-version": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-6.0.1.tgz", @@ -26036,6 +26243,11 @@ "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", "dev": true }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -26325,6 +26537,11 @@ "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", "dev": true }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, "truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -28060,6 +28277,62 @@ "string-width": "^4.0.0" } }, + "winston": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", + "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", + "requires": { + "async": "^2.6.1", + "diagnostics": "^1.1.1", + "is-stream": "^1.1.0", + "logform": "^2.1.1", + "one-time": "0.0.4", + "readable-stream": "^3.1.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.3.0" + }, + "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "winston-daily-rotate-file": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.4.2.tgz", + "integrity": "sha512-pVOUJKxN+Kn6LnOJZ4tTwdV5+N+fCkiRAb3bVnzcPtOj1ScxGNC3DyUhHuAHssBtMl5s45/aUcSUtApH+69V5A==", + "requires": { + "file-stream-rotator": "^0.5.7", + "object-hash": "^2.0.1", + "triple-beam": "^1.3.0", + "winston-transport": "^4.2.0" + } + }, + "winston-transport": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz", + "integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==", + "requires": { + "readable-stream": "^2.3.6", + "triple-beam": "^1.2.0" + } + }, "wmf-sitematrix": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/wmf-sitematrix/-/wmf-sitematrix-0.1.4.tgz", @@ -28166,9 +28439,9 @@ } }, "write-file-atomic": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.1.tgz", - "integrity": "sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "requires": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", diff --git a/package.json b/package.json index 0e84aa792..b13975fce 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,8 @@ "homepage": "https://github.com/digidem/mapeo-desktop#readme", "dependencies": { "@babel/runtime": "^7.1.2", + "@bugsnag/browser": "^7.1.1", + "@bugsnag/js": "^7.1.1", "@fortawesome/fontawesome-svg-core": "^1.2.24", "@fortawesome/free-solid-svg-icons": "^5.11.1", "@fortawesome/react-fontawesome": "^0.1.4", @@ -76,6 +78,7 @@ "@segment/isodate": "^1.0.3", "asar": "^1.0.0", "clsx": "^1.0.4", + "conf": "^6.2.4", "core-js": "^3.3.5", "d3-dsv": "^1.1.1", "date-fns": "^2.1.0", @@ -84,7 +87,6 @@ "electron-debug": "^1.5.0", "electron-is-dev": "^1.1.0", "electron-rabbit": "^1.0.0", - "electron-store": "^5.0.0", "electron-timber": "^0.5.1", "flat": "^5.0.0", "fs-write-stream-atomic": "^1.0.10", @@ -122,6 +124,8 @@ "tar-fs": "^1.16.0", "through2": "^2.0.5", "utm": "^1.1.1", + "winston": "^3.2.1", + "winston-daily-rotate-file": "^4.4.2", "yazl": "^2.5.1" }, "devDependencies": { diff --git a/src/background/index.js b/src/background/index.js index 9f71814c9..3e3ca4ef2 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -1,12 +1,31 @@ -const serverHandlers = require('./mapeo-ipc') const rabbit = require('electron-rabbit') +const serverHandlers = require('./mapeo-ipc') if (process.argv[2] === '--subprocess') { + const logger = require('../logger') const socketName = process.argv[4] + const userDataPath = process.argv[5] + if (!logger.configured) { + logger.configure({ + label: 'background', + userDataPath + }) + } rabbit.init(socketName, serverHandlers) } else { - const { ipcRenderer } = require('electron') - ipcRenderer.on('set-socket', (event, { name }) => { - rabbit.init(name, serverHandlers) + const electron = require('electron') + const logger = require('../logger') + + electron.ipcRenderer.on('configure', (event, { + socketName, isDev, userDataPath + }) => { + if (!logger.configured) { + logger.configure({ + label: 'electron-background', + userDataPath, + isDev + }) + } + rabbit.init(socketName, serverHandlers) }) } diff --git a/src/background/mapeo-ipc.js b/src/background/mapeo-ipc.js index 50f9c97da..31e148db5 100644 --- a/src/background/mapeo-ipc.js +++ b/src/background/mapeo-ipc.js @@ -1,10 +1,11 @@ +const rabbit = require('electron-rabbit') +const os = require('os') +const http = require('http') + const Mapeo = require('./mapeo') const createTileServer = require('./tile-server') -const rabbit = require('electron-rabbit') const logger = require('../logger') const createRouter = require('./server') -const os = require('os') -const http = require('http') class MapeoManager { constructor ({ datadir, userDataPath, port, tileport }) { @@ -16,45 +17,41 @@ class MapeoManager { listen (cb) { let pending = 2 - logger.log('manager.listen') this._createMapeo(() => { if (!this.server) { - logger.log('creating a server') this.server = http.createServer((req, res) => { - logger.log('server request', req.url) + logger.debug('server request', req.url) this.router(req, res) }) } this.server.listen(this.opts.port, '127.0.0.1', () => { - logger.log('osm-p2p-server listening on :', this.server.address().port) + logger.info('osm-p2p-server listening on :', this.server.address().port) if (--pending === 0) cb(this.server.address().port) }) }) this.tileServer = createTileServer(this.opts.userDataPath) this.tileServer.listen(this.opts.tileport, () => { - logger.log('tile server listening on :', this.tileServer.address().port) + logger.info('Tile server listening on :', this.tileServer.address().port) if (--pending === 0) cb(this.mapeo.server.address().port) }) } _createMapeo (cb) { var ipcSend = rabbit.send - logger.log('creating a new mapeo', this.opts) + logger.info('Creating a new mapeo', this.opts) this.mapeo = new Mapeo({ datadir: this.opts.datadir, userDataPath: this.opts.userDataPath, ipcSend }) - logger.log('encryption key', this.mapeo.encryptionKey) var { router, core } = createRouter(this.mapeo.osm, this.mapeo.media, { staticRoot: this.opts.userDataPath, ipcSend }) - logger.log('new router') this.router = router this.mapeo.listen(core, () => { @@ -66,10 +63,10 @@ class MapeoManager { } reloadConfig (cb) { - logger.log('closing old mapeo') + logger.debug('closing old mapeo') this.mapeo.close(() => { this.mapeo = null - logger.log('opening new one') + logger.debug('opening new one') this._createMapeo(cb) }) } @@ -98,8 +95,11 @@ var manager = null handlers['reload-config'] = async () => { return new Promise((resolve, reject) => { manager.reloadConfig((err) => { - logger.log('config reloaded', err) - if (err) reject(err) + if (err) { + logger.error('Reload config', err) + return reject(err) + } + logger.info('Configuration reloaded') resolve() }) }) @@ -108,10 +108,12 @@ handlers['reload-config'] = async () => { handlers.close = async () => { return new Promise((resolve, reject) => { if (!manager) return resolve() - logger.log('Closing!') manager.close((err) => { - logger.log('now its done', err) - if (err) return reject(err) + logger.info('CLOSED') + if (err) { + logger.error('Could not close', err) + return reject(err) + } resolve() }) }) @@ -120,10 +122,8 @@ handlers.close = async () => { handlers.listen = async (opts) => { if (!manager) manager = new MapeoManager(opts) return new Promise((resolve, reject) => { - logger.log('listening') - manager.listen((port) => { - logger.log('got port, resolving', port) + logger.info('Sync listening on ', port) resolve(port) }) }) @@ -132,8 +132,10 @@ handlers.listen = async (opts) => { handlers['import-tiles'] = async (filename) => { return new Promise((resolve, reject) => { manager.mapeo.tiles.go(filename, (err) => { - if (err) return reject(err) - else resolve() + if (err) { + logger.error(`mapeo.tiles.go(${filename})`, err) + return reject(err) + } else resolve() }) }) } @@ -161,11 +163,14 @@ handlers['sync-leave'] = async () => { } handlers['export-data'] = async (args) => { - logger.log('did i get this', args) + logger.info('Exporting data', args) return new Promise((resolve, reject) => { manager.mapeo.exportData(args, (err) => { - if (err) return reject(err) - else resolve() + if (err) { + logger.error(`mapeo.exportData(${args})`, err) + return reject(err) + } + resolve() }) }) } @@ -173,11 +178,17 @@ handlers['export-data'] = async (args) => { handlers['zoom-to-data-get-centroid'] = async (type) => { return new Promise((resolve, reject) => { manager.mapeo.getDatasetCentroid(type, (err, loc) => { - logger.log('RESPONSE(getDatasetCentroid):', loc) - if (err) return reject(err) + if (err) { + logger.error(`mapeo.getDatasetCentroid(${type})`, err) + return reject(err) + } resolve(loc) }) }) } +handlers.debugging = async (bool) => { + logger.debugging(bool) +} + module.exports = handlers diff --git a/src/background/mapeo.js b/src/background/mapeo.js index 561bb9753..705dca5f2 100644 --- a/src/background/mapeo.js +++ b/src/background/mapeo.js @@ -20,13 +20,12 @@ class MapeoRPC { this.storages = [] this.config = new Settings(userDataPath) this.encryptionKey = this.config.getEncryptionKey() - logger.log('got encryptionKey', this.encryptionKey) + logger.info('got encryptionKey', this.encryptionKey.substr(0, 4)) this.tiles = TileImporter(userDataPath) var feedsDir = path.join(datadir, 'storage') var indexDir = path.join(datadir, 'index') - logger.log('loading datadir', datadir) this.indexDb = level(indexDir) const coreDb = kappa(datadir, { @@ -57,7 +56,7 @@ class MapeoRPC { this.ipcSend = ipcSend this.ipcSend('indexes-loading') this.osm.ready(() => { - logger.log('indexes READY') + logger.debug('indexes READY') this.ipcSend('indexes-ready') }) @@ -96,7 +95,6 @@ class MapeoRPC { this.core.on('error', this._handleError) this.core.sync.listen((err) => { - logger.log('mapeo-core sync is listening') cb(err) }) } @@ -104,17 +102,19 @@ class MapeoRPC { _syncWatch (sync) { const startTime = Date.now() var onerror = (err) => { - logger.error(err) + logger.error('sync', err) + // We handle errors separately/differently for sync + // instead of sending to _handleError sync.removeListener('error', onerror) sync.removeListener('progress', this._throttledSendPeerUpdate) sync.removeListener('end', onend) } var onend = (err) => { - if (err) logger.error(err) + if (err) logger.error('sync error', err) this.ipcSend('sync-complete') const syncDurationSecs = ((Date.now() - startTime) / 1000).toFixed(2) - logger.log('Sync completed in ' + syncDurationSecs + ' seconds') + logger.info('Sync completed in ' + syncDurationSecs + ' seconds') sync.removeListener('error', onerror) sync.removeListener('progress', this._throttledSendPeerUpdate) sync.removeListener('end', onend) @@ -129,7 +129,7 @@ class MapeoRPC { _onNewPeer (peer) { this._throttledSendPeerUpdate(peer) if (!peer.sync) { - return logger.error('Could not monitor peer, missing sync property') + return logger.error('sync', new Error('Could not monitor peer, missing sync property')) } peer.sync.once('sync-start', () => { this._syncWatch(peer.sync) @@ -137,8 +137,7 @@ class MapeoRPC { } syncStart (target = {}) { - logger.log('Sync start request:', target) - logger.log('sync starting', target) + logger.info('Sync start request:', target) const sync = this.core.sync.replicate(target, { projectKey: this.encryptionKey @@ -149,31 +148,31 @@ class MapeoRPC { syncJoin () { try { - logger.log( + logger.debug( 'Joining swarm', this.encryptionKey && this.encryptionKey.slice(0, 4) ) this.core.sync.join(this.encryptionKey) } catch (e) { - logger.error('sync join error', e) + logger.error('syncJoin', e) } } syncLeave () { try { - logger.log( + logger.debug( 'Leaving swarm', this.encryptionKey && this.encryptionKey.slice(0, 4) ) this.core.sync.leave(this.encryptionKey) } catch (e) { - logger.error('sync leave error', e) + logger.error('syncLeave', e) } } exportData ({ filename, format, id }, cb) { const presets = this.config.getSettings('presets') || {} - logger.log('down here exporting', filename, format) + logger.info('Exporting', filename, format) this.core.exportData(filename, { format, presets }, cb) } @@ -193,7 +192,7 @@ class MapeoRPC { (peer.state.topic === 'replication-started' || peer.state.topic === 'replication-progress') ) - logger.log(currentlyReplicatingPeers.length + ' peers still replicating') + logger.info(currentlyReplicatingPeers.length + ' peers still replicating') if (currentlyReplicatingPeers.length === 0) { clearTimeout(timeoutId) return cb() @@ -205,11 +204,10 @@ class MapeoRPC { } getDatasetCentroid (type, done) { - logger.log('STATUS(getDatasetCentroid):', type) this.osm.core.api.stats.getMapCenter(type, function (err, center) { - if (err) return logger.error('ERROR(getDatasetCentroid):', err) + if (err) return logger.error(`api.stats.getMapCenter(${type})`, err) if (!center) return done(null, null) - logger.log('RESPONSE(getDatasetCentroid):', center) + logger.info('RESPONSE(getDatasetCentroid):', type, center) done(null, [center.lon, center.lat]) }) } @@ -236,7 +234,6 @@ class MapeoRPC { var done = () => { if (--pending) return - logger.log('all closed!') this.closing = false cb() } @@ -244,7 +241,7 @@ class MapeoRPC { _handleError (err) { if (typeof err === 'string') err = new Error(err) - logger.error(err) + logger.error('mapeo', err) this.ipcSend('error', err) } diff --git a/src/background/server.js b/src/background/server.js index 848d11714..4ba60a1a4 100644 --- a/src/background/server.js +++ b/src/background/server.js @@ -33,7 +33,7 @@ module.exports = function (osm, media, { ipcSend, staticRoot }) { if (!m) { staticHandler(req, res, function (err) { - if (err) logger.error(err) + if (err) logger.error('static', err) res.statusCode = 404 res.end('Not Found') }) diff --git a/src/background/tile-importer.js b/src/background/tile-importer.js index 83e64dfde..273d2ddb9 100644 --- a/src/background/tile-importer.js +++ b/src/background/tile-importer.js @@ -35,6 +35,8 @@ TileImporter.prototype.go = function (tilesPath, options, cb) { options = TileImporter.defaults } var self = this + // TODO: The caller should prevent double importing + // TODO: internationalize error (if this is going to user) if (self.editing) return cb(new Error('Tiles importing, please wait...')) self.editing = true options.id = options.id || options.name.replace(' ', '-') // what else should we normalize? @@ -46,7 +48,6 @@ TileImporter.prototype.go = function (tilesPath, options, cb) { }) function done (err) { - logger.error('ERROR(tile-importer)', err) self.editing = false cb(err) } @@ -110,11 +111,11 @@ TileImporter.prototype.moveTiles = function (tilesPath, tilesDest, cb) { } TileImporter.prototype._createAsar = function (tilesPath, destFile, cb) { - logger.log('creating asar', tilesPath, destFile) + logger.info('Creating asar', tilesPath, destFile) try { asar.createPackage(tilesPath, destFile).then(cb) } catch (err) { - logger.error('ERROR(tile-importer): Got error when creating asar', err) + logger.error('ERROR(tile-importer): Got error when creating asar' + tilesPath + ' ' + destFile, err) return cb(err) } } diff --git a/src/logger.js b/src/logger.js index aa5da11a1..4d082cdd4 100644 --- a/src/logger.js +++ b/src/logger.js @@ -1,8 +1,165 @@ -var logger -try { - logger = require('electron-timber') -} catch (err) { - // if electron not available, fall back to console - logger = console +const winston = require('winston') +const path = require('path') +const DailyRotateFile = require('winston-daily-rotate-file') +const util = require('util') + +const store = require('./store') +const appVersion = require('../package.json').version + +const BUGSNAG_API_KEY = 'fcd92279c11ac971b4bd29b646ec4125' + +let Bugsnag + +class Logger { + constructor () { + this.winston = winston.createLogger() + this.configured = false + this._messageQueue = [] + this._debug = store.get('debugging') + } + + configure ({ + userDataPath, + isDev, + label + }) { + this.isDev = isDev + this._startBugsnag(isDev ? 'development' : 'production') + const prettyPrint = winston.format.printf(({ level, message, label, timestamp }) => { + return `${timestamp} [${label}] ${level}: ${message}` + }) + + this.winston.format = winston.format.combine( + winston.format.label({ label }), + winston.format.timestamp(), + prettyPrint + ) + this.dirname = path.join(userDataPath, 'logs') + + for (const transport of this.winston.transports) { + if (transport.close) { + transport.close() + } + } + + const mainLog = new (DailyRotateFile)({ + filename: '%DATE%.log', + dirname: this.dirname, + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxFiles: '365d', + level: 'info' + }) + mainLog.name = 'main' + mainLog.on('new', () => { + // if debugging is on and a new log file is created, turn it off + this.debugging(false) + }) + mainLog.on('rotate', () => { + // if debugging is on during a rotate, turn it off + this.debugging(false) + }) + this.winston.add(mainLog) + + const errorTransport = new (DailyRotateFile)({ + filename: '%DATE%.error.log', + dirname: this.dirname, + datePattern: 'YYYY-MM', + zippedArchive: true, + maxFiles: '365d', + level: 'error' + }) + this.winston.add(errorTransport) + + if (isDev) { + this.winston.add(new winston.transports.Console({ + level: 'debug' + })) + } + this.configured = true + if (this._messageQueue.length > 0) this._drainQueue() + } + + debugging (debug) { + this._level(debug ? 'debug' : 'info') + store.set('debugging', debug) + this._debug = debug + } + + _level (level) { + var port = this.winston.transports.find(t => t.name === 'main') + if (port) port.level = level + else this.error('logger', new Error('Could not find transport main')) + } + + configured () { + return this.configured + } + + _drainQueue () { + while (this._messageQueue.length) { + var msg = this._messageQueue.pop() + this._log(msg.level, msg.args) + } + } + + _log (level, args) { + if (!this.configured) this._messageQueue.push({ level, args }) + else { + this.winston.log({ + level, + message: util.format(...args) + }) + if (level === 'error') { + logger.info('sending bugsnag', args[1]) + Bugsnag.notify(args[1], (event) => { + event.context = args[0] + }) + } + } + } + + warn () { + this._log('warn', Array.from(arguments)) + } + + error (context, err) { + if (!err) err = context + console.log('poop', context, err) + this._log('error', [context, err]) + } + + info () { + this._log('info', Array.from(arguments)) + } + + debug () { + this._log('debug', Array.from(arguments)) + } + + _startBugsnag (releaseStage) { + // This is really hacky because the @bugsnag/js package + // does not properly handle an electron background + // window with nodeIntegration: true + const args = { + releaseStage, + apiKey: BUGSNAG_API_KEY, + appVersion, + enabledReleaseStages: ['production'] + } + + setTimeout(function () {}).__proto__.unref = function () {} + try { + Bugsnag = require('@bugsnag/node') + Bugsnag.start(args) + } catch (err) { + logger.error('Failed to load bugsnag!', err) + Bugsnag = require('@bugsnag/browser') + Bugsnag.start(args) + } + } } + +const logger = new Logger() + module.exports = logger diff --git a/src/main/i18n.js b/src/main/i18n.js index 6244f5f19..0a35160bf 100644 --- a/src/main/i18n.js +++ b/src/main/i18n.js @@ -29,7 +29,7 @@ class I18n extends EventEmitter { translations[this.genericLocale] || translations[this.defaultLocale] if (!messages) { - logger.log('No translations for locale "' + locale + '"') + logger.debug('No translations for locale "' + locale + '"') return '[No translation]' } const message = @@ -38,14 +38,14 @@ class I18n extends EventEmitter { translations[this.genericLocale][id]) || (translations[this.defaultLocale] && translations[this.defaultLocale][id]) if (!message) { - logger.log(`No translations for '${id}' in locale '${locale}'`) + logger.debug(`No translations for '${id}' in locale '${locale}'`) return '[No translation]' } return message } setLocale (newLocale = this.defaultLocale) { - logger.log('Changing locale to [' + newLocale + ']') + logger.info('Changing locale to [' + newLocale + ']') this.locale = newLocale this.genericLocale = newLocale.split('-')[0] this.emit('locale-change', newLocale) @@ -60,6 +60,7 @@ app.once('ready', () => { try { i18n.setLocale(store.get('locale')) } catch (err) { + logger.error('i18n.setLocale ', err) i18n.setLocale(app.getLocale()) } }) diff --git a/src/main/ipc.js b/src/main/ipc.js index 069f1dc2d..718cd4f80 100644 --- a/src/main/ipc.js +++ b/src/main/ipc.js @@ -21,7 +21,7 @@ module.exports = function (win) { ipc.on('get-user-data', function (event, type) { var data = userConfig.getSettings(type) - if (!data) console.warn('unhandled event', type) + if (!data) logger.debug('unhandled event', type) event.returnValue = data }) @@ -47,7 +47,7 @@ module.exports = function (win) { ) userConfig.importSettings(filename, function (err) { if (err) return logger.error(err) - logger.log('Example presets imported from ' + filename) + logger.info('Example presets imported') }) }) diff --git a/src/main/menu.js b/src/main/menu.js index b493ba166..543708243 100644 --- a/src/main/menu.js +++ b/src/main/menu.js @@ -1,13 +1,12 @@ -const { dialog, app, Menu } = require('electron') +const { shell, dialog, app, Menu } = require('electron') const userConfig = require('./user-config') const i18n = require('./i18n') const logger = require('../logger') + const t = i18n.t module.exports = async function createMenu (ipc) { - // ipc is an object that is sending events to the handler in src/background/ipc.js - // NOT src/main/ipc.js, which fields requests from the electron renderer await app.whenReady() function setMenu () { @@ -46,7 +45,7 @@ function menuTemplate (ipc) { t('menu-import-tiles-error-known') + ': ' + err ) } else { - logger.log('[IMPORT TILES] success') + logger.debug('[IMPORT TILES] success') dialog.showMessageBox({ message: t('menu-import-data-success'), buttons: ['OK'] @@ -72,7 +71,7 @@ function menuTemplate (ipc) { userConfig.importSettings(filenames[0], cb) function cb (err) { if (!err) { - logger.log('reloading') + logger.debug('reloading') ipc.send('reload-config', null, () => { focusedWindow.webContents.send('force-refresh-window') }) @@ -103,7 +102,7 @@ function menuTemplate (ipc) { function (filenames) { if (!filenames || !filenames.length) return var filename = filenames[0] - logger.log('[IMPORTING]', filename) + logger.info('[IMPORTING]', filename) ipc.send('import-data', filename) } ) @@ -208,12 +207,12 @@ function menuTemplate (ipc) { label: t('menu-zoom-to-data'), click: function (item, focusedWindow) { ipc.send('zoom-to-data-get-centroid', 'node', function (_, loc) { - logger.log('RESPONSE(menu,getDatasetCentroid):', loc) + logger.debug('RESPONSE(menu,getDatasetCentroid):', loc) if (!loc) return focusedWindow.webContents.send('zoom-to-data-node', loc) }) ipc.send('zoom-to-data-get-centroid', 'observation', function (_, loc) { - logger.log('RESPONSE(menu,getDatasetCentroid):', loc) + logger.debug('RESPONSE(menu,getDatasetCentroid):', loc) if (!loc) return focusedWindow.webContents.send('zoom-to-data-observation', loc) }) @@ -248,7 +247,25 @@ function menuTemplate (ipc) { { label: t('menu-help'), role: 'help', - submenu: [] + submenu: [ + { + label: t('menu-debugging'), + type: 'checkbox', + checked: logger._debug, + click: function (item, focusedWindow) { + var bool = item.checked + logger.debugging(bool) + ipc.send('debugging', bool) + focusedWindow.webContents.send('debugging', bool) + } + }, + { + label: t('menu-report'), + click: function (item, focusedWindow) { + shell.openExternal('https://github.com/digidem/mapeo-desktop/issues/new?template=bug_report.md') + } + } + ] } ] diff --git a/src/main/user-config.js b/src/main/user-config.js index ab57484b4..e09f9a624 100644 --- a/src/main/user-config.js +++ b/src/main/user-config.js @@ -16,8 +16,8 @@ function getEncryptionKey () { const metadata = getSettings('metadata') const projectKey = metadata.projectKey if (projectKey) { - logger.log('Found projectKey starting with ', projectKey.slice(0, 4)) - } else logger.log("No projectKey found, using default 'mapeo' key") + logger.info('Found projectKey starting with ', projectKey.slice(0, 4)) + } else logger.info("No projectKey found, using default 'mapeo' key") return projectKey } diff --git a/src/renderer/app.js b/src/renderer/app.js index 0005f09e6..f67410e62 100644 --- a/src/renderer/app.js +++ b/src/renderer/app.js @@ -1,8 +1,9 @@ import React from 'react' import ReactDOM from 'react-dom' -import { ipcRenderer } from 'electron' +import { remote, ipcRenderer } from 'electron' import { StylesProvider, ThemeProvider } from '@material-ui/styles' import { IntlProvider } from 'react-intl' +import isDev from 'electron-is-dev' import CssBaseline from '@material-ui/core/CssBaseline' import logger from '../logger' @@ -29,6 +30,17 @@ const allMsgs = { pt: { ...mdMsgs.pt, ...mfMsgs.pt } } +if (!logger.configured) { + logger.configure({ + label: 'renderer', + userDataPath: remote.app.getPath('userData'), + isDev + }) + ipcRenderer.on('debugging', (ev, bool) => { + logger.debugging(bool) + }) +} + const App = () => { const [locale, setLocale] = React.useState(initialLocale) @@ -41,6 +53,7 @@ const App = () => { // refresh instead ipcRenderer.send('force-refresh-window') }, []) + logger.info('Rendering', locale) return ( @@ -58,7 +71,7 @@ ReactDOM.render(, document.getElementById('root')) const localStorage = window.localStorage window.testMode = function () { - logger.log('Test mode, clearing cache') + logger.debug('Test mode, clearing cache') localStorage.removeItem('lastView') localStorage.removeItem('location') } diff --git a/src/renderer/components/MapEditor/ExportButton.js b/src/renderer/components/MapEditor/ExportButton.js index db2103cde..ab76930db 100644 --- a/src/renderer/components/MapEditor/ExportButton.js +++ b/src/renderer/components/MapEditor/ExportButton.js @@ -84,7 +84,7 @@ const ExportButton = () => { }) .catch(err => { setStatus('reject') - logger.error(err) + logger.error('ExportButton save dialog', err) }) } ) diff --git a/src/renderer/components/MapFilter/DataExportDialog.js b/src/renderer/components/MapFilter/DataExportDialog.js index 1191e1b33..c7665d719 100644 --- a/src/renderer/components/MapFilter/DataExportDialog.js +++ b/src/renderer/components/MapFilter/DataExportDialog.js @@ -23,6 +23,7 @@ import fs from 'fs' import fsWriteStreamAtomic from 'fs-write-stream-atomic' import pump from 'pump' +import logger from '../../../logger' import createZip from '../../create-zip' const msgs = defineMessages({ @@ -129,7 +130,7 @@ const ExportDialogContent = ({ function onSelectFile (filepath) { if (values.photos === 'none') { fs.writeFile(filepath, exportData, err => { - if (err) console.error(err) + if (err) logger.error('DataExportDialog: onSelectFile', err) handleClose() }) return @@ -156,7 +157,7 @@ const ExportDialogContent = ({ const archive = createZip(localFiles, remoteFiles) pump(archive, output, err => { - if (err) console.error(err) + if (err) logger.error('DataExportDialog: pump create zip', err) handleClose() }) } diff --git a/src/renderer/components/MapFilter/MapExportDialog.js b/src/renderer/components/MapFilter/MapExportDialog.js index ddcc66ff3..3027ede73 100644 --- a/src/renderer/components/MapFilter/MapExportDialog.js +++ b/src/renderer/components/MapFilter/MapExportDialog.js @@ -80,9 +80,9 @@ const EditDialogContent = ({ ) createArchive(filepathWithExtension, err => { if (err) { - logger.error('Failed to create archive', err) + logger.error('MapExportDialog: Failed to create archive', err) } else { - logger.log('Successfully created map archive') + logger.debug('Successfully created map archive') } handleClose() }) diff --git a/src/renderer/components/MapFilter/MapFilter.js b/src/renderer/components/MapFilter/MapFilter.js index 726bd9cfa..0317ae3af 100644 --- a/src/renderer/components/MapFilter/MapFilter.js +++ b/src/renderer/components/MapFilter/MapFilter.js @@ -141,7 +141,7 @@ function usePresets () { setPresets(presetsWithFields) setFields(fields) } catch (e) { - logger.error(e) + logger.error('MapFilter get Presets', e) setLoading(false) setError(e) } @@ -272,8 +272,8 @@ const MapFilter = () => { ) } - if (observationsError) console.error(observationsError) - if (presetsError) console.error(presetsError) + if (observationsError) logger.error('observationsError', observationsError) + if (presetsError) logger.error('presetsError', presetsError) return (
diff --git a/src/renderer/components/SyncView/SyncAppBar.js b/src/renderer/components/SyncView/SyncAppBar.js index 36137ffe7..bd009b398 100644 --- a/src/renderer/components/SyncView/SyncAppBar.js +++ b/src/renderer/components/SyncView/SyncAppBar.js @@ -9,11 +9,13 @@ import { defineMessages, useIntl } from 'react-intl' import wifi from 'node-wifi' import { Tooltip } from '@material-ui/core' +import logger from '../../../logger' + let wifiInit = true try { wifi.init() } catch (e) { - console.error(e) + logger.error('Failed to init node-wifi', e) wifiInit = false } @@ -61,7 +63,7 @@ const SyncAppBar = ({ onClickSelectSyncfile, onClickNewSyncfile }) => { const conn = await wifi.getCurrentConnections() setCurrentConnection(conn && conn[0]) } catch (err) { - console.error(err) + logger.error('SyncAppBar failed to get current connections', err) setWifiError(true) setCurrentConnection(null) } diff --git a/src/renderer/components/SyncView/index.js b/src/renderer/components/SyncView/index.js index 00b5677e9..d078bc026 100644 --- a/src/renderer/components/SyncView/index.js +++ b/src/renderer/components/SyncView/index.js @@ -40,7 +40,7 @@ const SyncView = ({ focusState }) => { const listenForSyncPeers = focusState === 'focused' const [peers, syncPeer] = usePeers(listenForSyncPeers) const { formatMessage: t } = useIntl() - logger.log('render peers', peers) + logger.debug('render peers', peers) const handleClickSelectSyncfile = () => { remote.dialog.showOpenDialog( @@ -118,7 +118,7 @@ function usePeers (listen) { if (!listen) return const updatePeers = (updatedServerPeers = []) => { - logger.log('Received peer update', updatedServerPeers) + logger.debug('Received peer update', updatedServerPeers) setServerPeers(updatedServerPeers) // NB: use callback version of setState because the new error state // depends on the previous error state @@ -176,7 +176,7 @@ function usePeers (listen) { const syncPeer = useCallback( (peerId, opts) => { - logger.log('Request sync start', peerId, serverPeers) + logger.info('Request sync start', peerId, serverPeers) if (opts && opts.file) return api.syncStart({ filename: peerId }) const peer = serverPeers.find(peer => peer.id === peerId) // Peer could have vanished in the moment the button was pressed @@ -210,7 +210,7 @@ function getPeersStatus ({ since, formatMessage }) { - logger.log('get peers status', serverPeers, syncErrors) + logger.debug('get peers status', serverPeers, syncErrors) return serverPeers.map(serverPeer => { let status = peerStatus.READY let errorMsg diff --git a/src/renderer/create-zip.js b/src/renderer/create-zip.js index fc282f59c..3f6a96af1 100644 --- a/src/renderer/create-zip.js +++ b/src/renderer/create-zip.js @@ -32,7 +32,7 @@ export default function createZip (localFiles, remoteFiles) { const tasks = remoteFiles.map(({ url, metadataPath, ...options }) => cb => { cb = once(cb) const start = Date.now() - logger.log('Requesting', url) + logger.debug('Requesting', url) // I tried doing this by adding streams to the zipfile, but it's really hard // to catch errors when trying to download an image, so you end up with // corrupt files in the zip. This uses a bit more memory, but images are @@ -40,7 +40,7 @@ export default function createZip (localFiles, remoteFiles) { ky.get(url) .arrayBuffer() .then(arrBuf => { - logger.log('Req end in ' + (Date.now() - start) + 'ms ' + metadataPath) + logger.debug('Req end in ' + (Date.now() - start) + 'ms ' + metadataPath) zipfile.addBuffer(Buffer.from(arrBuf), metadataPath, { ...options, store: true @@ -49,14 +49,14 @@ export default function createZip (localFiles, remoteFiles) { }) .catch(err => { missing.push(metadataPath) - logger.log('Error downloading file ' + metadataPath, err) + logger.error('Error downloading file ' + metadataPath, err) cb() }) }) const start = Date.now() - logger.log('Starting download') + logger.debug('Starting download') run(tasks, concurrency, (...args) => { - logger.log('Downloaded images in ' + (Date.now() - start) + 'ms') + logger.info('Downloaded images in ' + (Date.now() - start) + 'ms') if (missing.length) { zipfile.addBuffer( Buffer.from(missing.join('\r\n') + '\r\n'), diff --git a/src/renderer/new-api.js b/src/renderer/new-api.js index e37dd17aa..75f834661 100644 --- a/src/renderer/new-api.js +++ b/src/renderer/new-api.js @@ -33,7 +33,7 @@ function Api ({ baseUrl, mapeo, ipc }) { const start = Date.now() promise .then(data => { - logger.log(prefix, Date.now() - start + 'ms') + logger.debug(prefix, Date.now() - start + 'ms') }) .catch(error => { logger.error(prefix, error) @@ -48,11 +48,9 @@ function Api ({ baseUrl, mapeo, ipc }) { return logRequest('PUT:', url, data) return logRequest('POST:', url, data) return logRequest(' { ipc.send('export-data', { filename, format }, (err) => { if (err) { - logger.error('Export error', err.stack) + logger.error('export data', err) reject(err) } else resolve() }) diff --git a/src/store.js b/src/store.js index 45f1cbb71..3b469dae2 100644 --- a/src/store.js +++ b/src/store.js @@ -1,5 +1,9 @@ -const Store = require('electron-store') +const Store = require('conf') -const store = new Store({ name: 'mapeo-settings' }) +const store = new Store({ + projectName: 'Mapeo', + projectSuffix: '', + configName: 'mapeo-settings' +}) module.exports = store