From 229f79e837ab42915e81e15a63f526ce210d5a92 Mon Sep 17 00:00:00 2001 From: Will Bradley Date: Sat, 1 Sep 2018 15:59:11 -0700 Subject: [PATCH] old logger was blocking indefinitely. made new logger. --- .gitignore | 1 + package-lock.json | 273 ++++++++++++++++++++++++++++++++++++++---- package.json | 3 +- public/client.js | 13 +- src/models/door.js | 2 +- src/models/logger.js | 65 ++++++++++ src/routes/checkin.js | 32 ++--- src/routes/logs.js | 7 +- src/server.js | 1 - src/views/home.pug | 2 +- src/views/logs.pug | 11 +- 11 files changed, 359 insertions(+), 51 deletions(-) create mode 100644 src/models/logger.js diff --git a/.gitignore b/.gitignore index c0de0d8..ca8881e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ cards.json logs.json +logs.log .env node_modules diff --git a/package-lock.json b/package-lock.json index 50904f4..d1656b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5861,7 +5861,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -6276,7 +6277,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -6332,6 +6334,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6375,12 +6378,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -11231,7 +11236,6 @@ "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11246,8 +11250,7 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", @@ -11258,8 +11261,7 @@ "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -11376,8 +11378,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -11389,7 +11390,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -11404,7 +11404,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -11412,14 +11411,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -11438,7 +11435,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -11519,8 +11515,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -11532,7 +11527,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -11654,7 +11648,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -15099,6 +15092,244 @@ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" } } + }, + "winston": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.0.0.tgz", + "integrity": "sha512-7QyfOo1PM5zGL6qma6NIeQQMh71FBg/8fhkSAePqtf5YEi6t+UrPDcUuHhuuUasgso49ccvMEsmqr0GBG2qaMQ==", + "requires": { + "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" + }, + "dependencies": { + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "requires": { + "lodash": "^4.17.10" + } + }, + "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=" + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colornames": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", + "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" + }, + "colors": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.2.tgz", + "integrity": "sha512-rhP0JSBGYvpcNQj4s5AdShMeE5ahMop96cTeDl/v9qQQm2fYClE2QXZRi8wLzc+GmXSxdIqqbOIAhyObEXDbfQ==" + }, + "colorspace": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.1.tgz", + "integrity": "sha512-pI3btWyiuz7Ken0BWh9Elzsmv2bM9AhA7psXib4anUXy/orfZ/E0MbQwhSOG/9L8hLlalqrU0UhOuqxW1YjmVw==", + "requires": { + "color": "3.0.x", + "text-hex": "1.0.x" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "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" + } + }, + "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" + } + }, + "env-variable": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.4.tgz", + "integrity": "sha512-+jpGxSWG4vr6gVxUHOc4p+ilPnql7NzZxOZBxNldsKGjCF+97df3CbuX7XMaDa5oAVkKQj4rKp38rYdC4VcpDg==" + }, + "fast-safe-stringify": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz", + "integrity": "sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==" + }, + "fecha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "kuler": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.0.tgz", + "integrity": "sha512-oyy6pu/yWRjiVfCoJebNUKFL061sNtrs9ejKTbirIwY3oiHmENVCSkHhxDV85Dkm7JYR/czMCBeoM87WilTdSg==", + "requires": { + "colornames": "^1.1.1" + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + }, + "logform": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/logform/-/logform-1.9.1.tgz", + "integrity": "sha512-ZHrZE8VSf7K3xKxJiQ1aoTBp2yK+cEbFcgarsjzI3nt3nE/3O0heNSppoOQMUJVMZo/xiVwCxiXIabaZApsKNQ==", + "requires": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.2.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "one-time": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", + "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "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==" + }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "winston-transport": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.2.0.tgz", + "integrity": "sha512-0R1bvFqxSlK/ZKTH86nymOuKv/cT1PQBMuDdA7k7f0S9fM44dNH6bXnuxwXPrN8lefJgtZq08BKdyZ0DZIy/rg==", + "requires": { + "readable-stream": "^2.3.6", + "triple-beam": "^1.2.0" + } + } + } } } } diff --git a/package.json b/package.json index fc8406f..9ad3166 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "moment": "2.22.2", "node-hid": "0.7.3", "pug": "2.0.3", - "serialport": "6.2.0" + "serialport": "6.2.0", + "winston": "^3.0.0" }, "devDependencies": { "babel-eslint": "8.2.2", diff --git a/public/client.js b/public/client.js index 21dc01c..7a20fe6 100644 --- a/public/client.js +++ b/public/client.js @@ -35,10 +35,10 @@ $(function(){ failure(); } } else { - error(); + error(data); } - }, "json").fail(function(){ - error(); + }, "json").fail(function(res){ + error(res.responseText); }).always(function(){ loading(false); window.checkinLoading = false; @@ -65,6 +65,7 @@ function reset(){ $("#failure").addClass("dn"); $("#success").addClass("dn"); $("#error").addClass("dn"); + $("#errormessage").text("Something went wrong with the card reader."); } function success(name){ @@ -84,10 +85,14 @@ function failure(){ }, 250); } -function error(){ +function error(msg){ + console.log(msg); reset(); setTimeout(function(){ $("#error").removeClass("dn"); + if (msg) { + $("#errormessage").text(msg); + } setTimeout(function(){ reset(); }, 6000); // 6000 to match door.js(const DELAY) }, 250); } \ No newline at end of file diff --git a/src/models/door.js b/src/models/door.js index 1120e75..e19b948 100644 --- a/src/models/door.js +++ b/src/models/door.js @@ -7,12 +7,12 @@ module.exports = class Door { return new Promise(success => { console.log('OPEN DOOR!') const board = new Board(2) - board.allOn() setTimeout(() => { console.log('CLOSING DOOR!') board.allOff() success() }, DELAY) + return board.allOn() }) } diff --git a/src/models/logger.js b/src/models/logger.js new file mode 100644 index 0000000..e6911a0 --- /dev/null +++ b/src/models/logger.js @@ -0,0 +1,65 @@ +var winston = require('winston'); // for transports.Console +const fs = require('fs') +const path = require('path') +const LOGS_PATH = path.join(process.cwd(), 'logs.log') + +var winston = winston.createLogger({ + transports: [ + new (winston.transports.Console)({ level: 'debug' }), + new (winston.transports.File)({ filename: 'logs.log', level: 'debug' }) + ] +}); + +winston.logError = function(card, message) { + winston.log({ + level: 'error', + message: message, + timestamp: new Date().getTime(), + card: card, + }) +} + +winston.logGrantedCard = function(card) { + winston.log({ + level: 'info', + message: 'granted', + timestamp: new Date().getTime(), + card: card + }) +} + +winston.logRejectedCard = function(card) { + winston.log({ + level: 'info', + message: 'rejected', + timestamp: new Date().getTime(), + card: card + }) +} + +winston.readLogs = function () { + return new Promise((resolve, reject) => { + // specify "utf8" to return a string + fs.readFile(LOGS_PATH, "utf8", (err, data) => { + if (err) return resolve(JSON.parse("[]")) + var out = [] + var split = data.split("\n") + // console.log(split) + for(var i=0; i { const rfid = req.body.rfid.trim().toLowerCase() @@ -8,26 +8,30 @@ module.exports = (req, res) => { Cards.validate(rfid).then(card => { console.log('CARD:', card) - if (!card) return reponse(req,res,'/failure',false) + if (!card) { + logger.logRejectedCard({number: rfid}) + return reponse(req,res,'/failure',false) + } - reponse(req,res,'/success?name=' + card.name,true,card.name) - Logs.log({ timestamp: new Date().getTime(), card }).then(() => - console.log('Logged!') - ) - Door.open() + logger.logGrantedCard(card) + Door.open().then(resp => { + reponse(req,res,'/success?name=' + card.name,true,card.name) + }).catch(err => { + logger.logError(card, err.message) + res.status(500).send("Error: "+err) + }) }).catch(err => { - reponse(req,res,'/failure',false) - Logs.log({ timestamp: new Date().getTime(), err}).then(() => - console.log('Undefined failure: '+err) - ) + logger.logError({number: rfid}, err.message) + res.status(500).send("Error: "+err) }) } -function reponse(req,res,path,success,name=null) { - console.log(req.get('accept')); +function reponse(req,res,path,success,name=null,message=null) { if(/application\/json/.test(req.get('accept'))) { - res.json({"success":success, "path":path, "name":name}) + console.log("returning json", success) + res.json({"success":success, "path":path, "name":name, "message": message}) } else { + console.log("returning redir") res.redirect(path) } } \ No newline at end of file diff --git a/src/routes/logs.js b/src/routes/logs.js index 2957272..3df8965 100644 --- a/src/routes/logs.js +++ b/src/routes/logs.js @@ -1,8 +1,9 @@ -const Logs = require('../models/logs') +const logger = require('../models/logger') module.exports = (req, res) => { - Logs.all().then(logs => { - logs = logs.sort((a, b) => a.timestamp < b.timestamp) + + logger.readLogs().then(logs => { + logs = logs.reverse() res.render('logs', { logs }) }) } diff --git a/src/server.js b/src/server.js index 8c249cc..2e71cc6 100644 --- a/src/server.js +++ b/src/server.js @@ -3,7 +3,6 @@ const express = require('express') const helmet = require('helmet') const moment = require('moment') const path = require('path') - const app = express() const PORT = process.env.PORT || 3000 diff --git a/src/views/home.pug b/src/views/home.pug index ff8bf7e..a2a294b 100644 --- a/src/views/home.pug +++ b/src/views/home.pug @@ -32,7 +32,7 @@ block content i.fas.fa-exclamation.mr-lg.txt-xxl .f-1 .txt-xl Error - .mt-sm.warning-lighter Something went wrong with the card reader. + #errormessage.mt-sm.warning-lighter Something went wrong with the card reader. .mt-sm.warning-lighter em We're sorry. Please contact a volunteer. diff --git a/src/views/logs.pug b/src/views/logs.pug index eb69931..48b2be7 100644 --- a/src/views/logs.pug +++ b/src/views/logs.pug @@ -10,15 +10,16 @@ block content thead tr th.px-md.py-sm.bb.bc-gray-lighter.gray-light.fw-1 Date + th.px-md.py-sm.bb.bc-gray-lighter.gray-light.fw-1 Type + th.px-md.py-sm.bb.bc-gray-lighter.gray-light.fw-1 Status th.px-md.py-sm.bb.bc-gray-lighter.gray-light.fw-1 Member Name th.px-md.py-sm.bb.bc-gray-lighter.gray-light.fw-1 RFID Card Number tbody for log in logs - - date = new Date(log.timestamp) + - date = log.timestamp ? new Date(log.timestamp) : "" tr.hov-bg-gray-lightest - td.px-md.py-sm.bb.bc-gray-lighter.c-help(title=date)= moment(date).fromNow() + td.px-md.py-sm.bb.bc-gray-lighter.c-help(title=date)= date ? moment(date).fromNow() : "" + td.px-md.py-sm.bb.bc-gray-lighter= log.level + td.px-md.py-sm.bb.bc-gray-lighter= log.message td.px-md.py-sm.bb.bc-gray-lighter= log.card ? log.card.name : "" td.px-md.py-sm.bb.bc-gray-lighter= log.card ? log.card.number : "" - td.px-md.py-sm.bb.bc-gray-lighter= log.err ? log.err.code : "" - td.px-md.py-sm.bb.bc-gray-lighter= log.err ? log.err.syscall : "" - td.px-md.py-sm.bb.bc-gray-lighter= log.err ? log.err.path : ""