diff --git a/packages-backend/express/README.md b/packages-backend/express/README.md index a3b87161c2..da0920e8c6 100644 --- a/packages-backend/express/README.md +++ b/packages-backend/express/README.md @@ -51,7 +51,7 @@ app.use((request, response, next) => { - `inferIssuer` (_boolean_): Determines whether the issuer should be inferred from the custom request HTTP header `x-mc-api-cloud-identifier` which is sent by the Merchant Center API Gateway when forwarding the request. This might be useful in case the server is used in multiple regions. -- `jwks` (_object_): see options of `jwks-rsa`. +- `jwks` (_object_): See options of `jwks-rsa`. ### Usage in Serverless Functions diff --git a/packages-backend/loggers/.gitignore b/packages-backend/loggers/.gitignore new file mode 100644 index 0000000000..c795b054e5 --- /dev/null +++ b/packages-backend/loggers/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/packages-backend/loggers/LICENSE b/packages-backend/loggers/LICENSE new file mode 100644 index 0000000000..7b4a3a5756 --- /dev/null +++ b/packages-backend/loggers/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 commercetools GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages-backend/loggers/README.md b/packages-backend/loggers/README.md new file mode 100644 index 0000000000..c0a2e93147 --- /dev/null +++ b/packages-backend/loggers/README.md @@ -0,0 +1,73 @@ +# @commercetools-backend/loggers + +

+ Latest release (latest dist-tag) Latest release (next dist-tag) Minified + GZipped size GitHub license +

+ +Opinionated JSON loggers for HTTP server applications. + +## Install + +```bash +$ npm install --save @commercetools-backend/loggers +``` + +## Access logger + +Creates a logger to be used for HTTP requests access logs. + +```js +const { createAccessLogger } = require('@commercetools-backend/loggers'); + +app.use(createAccessLogger()); +``` + +### Access logger options + +- `ignoreUrls` (_Array of string_): A list of URL paths to be ignored from being logged. + +## Application logger + +Creates a logger to be used programmatically in the application code. + +```js +const { createApplicationLogger } = require('@commercetools-backend/loggers'); + +const app = createApplicationLogger(); + +app.info('Hey there', { meta: { name: 'Tom' } }); +``` + +## Error report logger (Sentry) + +Creates a logger to be used for error reporting with Sentry. + +```js +const { createErrorReportLogger } = require('@commercetools-backend/loggers'); + +const { sentryRequestHandler } = createErrorReportLogger(); + +app.use(sentryRequestHandler); +``` + +```js +const { createErrorReportLogger } = require('@commercetools-backend/loggers'); + +const { trackError } = createErrorReportLogger(); + +trackError(error, { request }, (errorId) => { + if (errorId) { + // Attach the Sentry error id to the custom response header + response.setHeader('X-Sentry-Error-Id', errorId); + } + response.end(); +}); +``` + +### Error report logger options + +- `sentry` (_object_): An optional configuration object for Sentry. + - `sentry.DSN` (_string_): The DSN value of your Sentry project. + - `sentry.role` (_string_): The value for the `role` Sentry tag. + - `sentry.environment` (_string_): The value for the `environment` Sentry tag. +- `errorMessageBlacklist` (_Array of string or RegExp_): A list of error messages for which the error should not be reported, if the error message matches. diff --git a/packages-backend/loggers/index.ts b/packages-backend/loggers/index.ts new file mode 100644 index 0000000000..b20ac251bc --- /dev/null +++ b/packages-backend/loggers/index.ts @@ -0,0 +1,5 @@ +// This file exists because we want jest to use our non-compiled code to run tests +// if this file is missing, and you have a `module` or `main` that points to a non-existing file +// (ie, a bundle that hasn't been built yet) then jest will fail if the bundle is not yet built. +// all apps should export all their named exports from their root index.js +export * from './src'; diff --git a/packages-backend/loggers/package.json b/packages-backend/loggers/package.json new file mode 100644 index 0000000000..6a996a6c02 --- /dev/null +++ b/packages-backend/loggers/package.json @@ -0,0 +1,39 @@ +{ + "name": "@commercetools-backend/loggers", + "version": "1.0.0", + "description": "Opinionated JSON loggers for HTTP server applications", + "bugs": "https://github.com/commercetools/merchant-center-application-kit/issues", + "repository": { + "type": "git", + "url": "https://github.com/commercetools/merchant-center-application-kit.git", + "directory": "packages-backend/loggers" + }, + "homepage": "https://docs.commercetools.com/custom-applications", + "keywords": ["javascript", "nodejs", "express", "logger", "server", "toolkit"], + "license": "MIT", + "private": false, + "publishConfig": { + "access": "public" + }, + "main": "./build/index.js", + "typings": "./build/index.d.ts", + "types": "./build/index.d.ts", + "files": ["build", "package.json", "LICENSE", "README.md"], + "scripts": { + "prebuild": "rimraf build/**", + "build": "tsc -p tsconfig.build.json" + }, + "dependencies": { + "@sentry/node": "5.15.5", + "@types/triple-beam": "1.3.0", + "express-winston": "4.0.3", + "fast-safe-stringify": "2.0.7", + "lodash": "4.17.15", + "logform": "2.1.2", + "triple-beam": "1.3.0", + "winston": "3.2.1" + }, + "devDependencies": { + "express": "4.17.1" + } +} diff --git a/packages-backend/loggers/src/create-access-logger.ts b/packages-backend/loggers/src/create-access-logger.ts new file mode 100644 index 0000000000..868d75326b --- /dev/null +++ b/packages-backend/loggers/src/create-access-logger.ts @@ -0,0 +1,51 @@ +import type { RequestFilter } from 'express-winston'; + +import expressWinston from 'express-winston'; +import winston from 'winston'; +import * as env from './env'; +import redactInsecureRequestHeaders from './utils/redact-insecure-request-headers'; +import jsonFormatter from './custom-formats/json'; + +const shouldLog = !env.isTest || process.env.DEBUG === 'true'; + +type LoggerOptions = { + ignoreUrls?: string[]; +}; + +const defaultIgnoreUrls = ['/', '/health', '/favicon.ico']; +const defaultFormat = winston.format.combine(winston.format.timestamp()); + +// Inspired by https://github.com/bithavoc/express-winston/issues/62#issuecomment-396906056 +const requestFilter: RequestFilter = (req, propName) => { + if (propName === 'headers') { + return redactInsecureRequestHeaders(req); + } + return req[propName]; +}; + +const createAccessLogger = (options: LoggerOptions = {}) => { + const ignoreUrls = [...defaultIgnoreUrls, ...(options.ignoreUrls ?? [])]; + + return expressWinston.logger({ + transports: [new winston.transports.Console()], + format: winston.format.combine( + defaultFormat, + env.isDev ? winston.format.cli() : jsonFormatter() + ), + requestFilter, + meta: true, + expressFormat: true, // Use default morgan access log formatting + colorize: env.isDev, + skip: (req) => !shouldLog || ignoreUrls.includes(req.originalUrl), + dynamicMeta: (req) => ({ + ip: req.ip, + ips: req.ips, + hostname: req.hostname, + ...(req.connection.remoteAddress + ? { remoteAddress: req.connection.remoteAddress } + : {}), + }), + }); +}; + +export default createAccessLogger; diff --git a/packages-backend/loggers/src/create-application-logger.ts b/packages-backend/loggers/src/create-application-logger.ts new file mode 100644 index 0000000000..36964f996f --- /dev/null +++ b/packages-backend/loggers/src/create-application-logger.ts @@ -0,0 +1,21 @@ +import winston from 'winston'; +import * as env from './env'; +import jsonFormatter from './custom-formats/json'; + +const shouldLog = !env.isTest || process.env.DEBUG === 'true'; + +const createApplicationLogger = () => { + return winston.createLogger({ + level: process.env.DEBUG === 'true' ? 'debug' : 'info', + format: env.isDev + ? winston.format.combine(winston.format.cli(), winston.format.simple()) + : jsonFormatter(), + transports: [ + new winston.transports.Console({ + silent: !shouldLog, + }), + ], + }); +}; + +export default createApplicationLogger; diff --git a/packages-backend/loggers/src/create-error-report-logger.ts b/packages-backend/loggers/src/create-error-report-logger.ts new file mode 100644 index 0000000000..72631cf2db --- /dev/null +++ b/packages-backend/loggers/src/create-error-report-logger.ts @@ -0,0 +1,89 @@ +import type { Request, Response, NextFunction } from 'express'; + +import * as Sentry from '@sentry/node'; +import * as env from './env'; +import redactInsecureRequestHeaders from './utils/redact-insecure-request-headers'; + +const shouldLog = !env.isTest || process.env.DEBUG === 'true'; + +type LoggerOptions = { + sentry?: { + DSN: string; + role: string; + environment: string; + }; + errorMessageBlacklist?: Array; +}; + +function createErrorReportLogger(options: LoggerOptions = {}) { + // Sentry + if (options.sentry) { + Sentry.init({ dsn: options.sentry.DSN }); + Sentry.configureScope((scope) => { + scope.setLevel(Sentry.Severity.Error); + }); + Sentry.setTag('role', options.sentry.role); + Sentry.setTag('environment', options.sentry.environment); + } + + // Filter out errors that contains those strings either as name or message + const errorMessageBlacklist = options.errorMessageBlacklist ?? []; + + const shouldErrorBeTracked = (error: Error) => + !errorMessageBlacklist.some( + // The match can be either the error code as well as a part of + // the error message (in case the error code is not enough). + // Since it can be a mix of both, we need to match it + // to either the name or message. + (match) => { + if (typeof match === 'string') { + return match === error.name || match === error.message; + } + return match.test(error.name) || match.test(error.message); + } + ); + + function trackError( + error: Error, + meta: { request: Request; userId?: string }, + getErrorId: (errorId?: string | null) => void + ) { + if (!shouldLog) { + getErrorId(null); + return; + } + if (options.sentry && shouldErrorBeTracked(error)) { + if (meta.userId) { + Sentry.setUser({ id: meta.userId }); + } + const { method, originalUrl, httpVersion } = meta.request; + Sentry.setExtra('request', { + method, + originalUrl, + httpVersion, + headers: redactInsecureRequestHeaders(meta.request), + }); + const errorId = Sentry.captureException(error); + getErrorId(errorId); + } else { + getErrorId(); + } + } + + const passThroughMiddleware = ( + _req: Request, + _res: Response, + next: NextFunction + ) => next(); + + function sentryRequestHandler() { + if (shouldLog && options.sentry) { + return Sentry.Handlers.requestHandler({ request: false }); + } + return passThroughMiddleware; + } + + return { sentryRequestHandler, trackError }; +} + +export default createErrorReportLogger; diff --git a/packages-backend/loggers/src/custom-formats/json.ts b/packages-backend/loggers/src/custom-formats/json.ts new file mode 100644 index 0000000000..b54e36d230 --- /dev/null +++ b/packages-backend/loggers/src/custom-formats/json.ts @@ -0,0 +1,54 @@ +// This file is an exteded version of the `json` formatter so that we +// can rename the `level` field. +import type { TransformableInfo } from 'logform'; + +import { format } from 'logform'; +import { MESSAGE } from 'triple-beam'; +import jsonStringify from 'fast-safe-stringify'; +import getIn from 'lodash/get'; +import setIn from 'lodash/set'; +import unsetIn from 'lodash/unset'; + +/* + * function replacer (key, value) + * Handles proper stringification of Buffer output. + */ +function replacer(key: string, value: Buffer | string) { + if (key === 'queryJsonString') { + // We need to stringify the value as otherwise Kibana cardinality explodes. + return JSON.stringify(value); + } + return value instanceof Buffer ? value.toString('base64') : value; +} + +function replaceField( + info: TransformableInfo, + jsonPath: string, + newJsonPath: string +) { + const val = getIn(info, jsonPath); + if (val) { + unsetIn(info, jsonPath); + setIn(info, newJsonPath, val); + } +} + +/* + * function json (info) + * Returns a new instance of the JSON format that turns a log `info` + * object into pure JSON. This was previously exposed as { json: true } + * to transports in `winston < 3.0.0`. + */ +/* eslint-disable no-param-reassign */ +const jsonFormatter = format((info, opts = {}) => { + info.logLevel = info.level; + delete info.level; + + // Replace / rename fields + replaceField(info, 'meta.req.query', 'meta.req.queryJsonString'); + + info[MESSAGE] = jsonStringify(info, opts.replacer ?? replacer, opts.space); + return info; +}); + +export default jsonFormatter; diff --git a/packages-backend/loggers/src/env.ts b/packages-backend/loggers/src/env.ts new file mode 100644 index 0000000000..ef1dd55c5e --- /dev/null +++ b/packages-backend/loggers/src/env.ts @@ -0,0 +1,6 @@ +const env = process.env.NODE_ENV; +const isDev = !env || env === 'development'; +const isProd = env === 'production'; +const isTest = env === 'test'; + +export { isDev, isProd, isTest }; diff --git a/packages-backend/loggers/src/index.ts b/packages-backend/loggers/src/index.ts new file mode 100644 index 0000000000..2a5b93862f --- /dev/null +++ b/packages-backend/loggers/src/index.ts @@ -0,0 +1,4 @@ +export { default as createAccessLogger } from './create-access-logger'; +export { default as createApplicationLogger } from './create-application-logger'; +export { default as createErrorReportLogger } from './create-error-report-logger'; +export { default as redactInsecureRequestHeaders } from './utils/redact-insecure-request-headers'; diff --git a/packages-backend/loggers/src/utils/redact-insecure-request-headers.ts b/packages-backend/loggers/src/utils/redact-insecure-request-headers.ts new file mode 100644 index 0000000000..36965a95e1 --- /dev/null +++ b/packages-backend/loggers/src/utils/redact-insecure-request-headers.ts @@ -0,0 +1,20 @@ +import type { Request } from 'express'; + +const redacted = '[REDACTED]'; + +function redactInsecureRequestHeaders(request: Request) { + // Pick the headers that we want to redact, usually headers including sensitive information + const authHeader = request.header('authorization'); + const cookieHeader = request.header('cookie'); + + const redactedHeaders: { authorization?: string; cookie?: string } = {}; + if (authHeader) redactedHeaders.authorization = redacted; + if (cookieHeader) redactedHeaders.cookie = redacted; + + return { + ...request.headers, + ...redactedHeaders, + }; +} + +export default redactInsecureRequestHeaders; diff --git a/packages-backend/loggers/tsconfig.build.json b/packages-backend/loggers/tsconfig.build.json new file mode 100644 index 0000000000..d45f30e39a --- /dev/null +++ b/packages-backend/loggers/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationDir": "build", + "outDir": "build" + }, + "exclude": [ + "**/*.spec.ts" + ], +} \ No newline at end of file diff --git a/packages-backend/loggers/tsconfig.json b/packages-backend/loggers/tsconfig.json new file mode 100644 index 0000000000..aabf0e3a5f --- /dev/null +++ b/packages-backend/loggers/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.json", +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index dfdf4aacdd..e4e829bba0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5179,6 +5179,18 @@ dependencies: any-observable "^0.3.0" +"@sentry/apm@5.15.5": + version "5.15.5" + resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.15.5.tgz#dc0515f16405de52b3ba0d26f8a6dc2fcefe5fcc" + integrity sha512-2PyifsiQdvFEQhbL7tQnCKGLOO1JtZeqso3bc6ARJBvKxM77mtyMo/D0C2Uzt9sXCYiALhQ1rbB1aY8iYyglpg== + dependencies: + "@sentry/browser" "5.15.5" + "@sentry/hub" "5.15.5" + "@sentry/minimal" "5.15.5" + "@sentry/types" "5.15.5" + "@sentry/utils" "5.15.5" + tslib "^1.9.3" + "@sentry/browser@5.15.4": version "5.15.4" resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.15.4.tgz#5a7e7bad088556665ed8e69bceb0e18784e4f6c7" @@ -5189,6 +5201,16 @@ "@sentry/utils" "5.15.4" tslib "^1.9.3" +"@sentry/browser@5.15.5": + version "5.15.5" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.15.5.tgz#d9a51f1388581067b50d30ed9b1aed2cbb333a36" + integrity sha512-rqDvjk/EvogfdbZ4TiEpxM/lwpPKmq23z9YKEO4q81+1SwJNua53H60dOk9HpRU8nOJ1g84TMKT2Ov8H7sqDWA== + dependencies: + "@sentry/core" "5.15.5" + "@sentry/types" "5.15.5" + "@sentry/utils" "5.15.5" + tslib "^1.9.3" + "@sentry/core@5.15.4": version "5.15.4" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.15.4.tgz#08b617e093a636168be5aebad141d1f744217085" @@ -5200,6 +5222,17 @@ "@sentry/utils" "5.15.4" tslib "^1.9.3" +"@sentry/core@5.15.5": + version "5.15.5" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.15.5.tgz#40ea79bff5272d3fbbeeb4a98cdc59e1adbd2c92" + integrity sha512-enxBLv5eibBMqcWyr+vApqeix8uqkfn0iGsD3piKvoMXCgKsrfMwlb/qo9Ox0lKr71qIlZVt+9/A2vZohdgnlg== + dependencies: + "@sentry/hub" "5.15.5" + "@sentry/minimal" "5.15.5" + "@sentry/types" "5.15.5" + "@sentry/utils" "5.15.5" + tslib "^1.9.3" + "@sentry/hub@5.15.4": version "5.15.4" resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.15.4.tgz#cb64473725a60eec63b0be58ed1143eaaf894bee" @@ -5209,6 +5242,15 @@ "@sentry/utils" "5.15.4" tslib "^1.9.3" +"@sentry/hub@5.15.5": + version "5.15.5" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.15.5.tgz#f5abbcdbe656a70e2ff02c02a5a4cffa0f125935" + integrity sha512-zX9o49PcNIVMA4BZHe//GkbQ4Jx+nVofqU/Il32/IbwKhcpPlhGX3c1sOVQo4uag3cqd/JuQsk+DML9TKkN0Lw== + dependencies: + "@sentry/types" "5.15.5" + "@sentry/utils" "5.15.5" + tslib "^1.9.3" + "@sentry/minimal@5.15.4": version "5.15.4" resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.15.4.tgz#113f01fefb86b7830994c3dfa7ad4889ba7b2003" @@ -5218,7 +5260,31 @@ "@sentry/types" "5.15.4" tslib "^1.9.3" -"@sentry/types@5.15.4": +"@sentry/minimal@5.15.5": + version "5.15.5" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.15.5.tgz#a0e4e071f01d9c4d808094ae7203f6c4cca9348a" + integrity sha512-zQkkJ1l9AjmU/Us5IrOTzu7bic4sTPKCatptXvLSTfyKW7N6K9MPIIFeSpZf9o1yM2sRYdK7GV08wS2eCT3JYw== + dependencies: + "@sentry/hub" "5.15.5" + "@sentry/types" "5.15.5" + tslib "^1.9.3" + +"@sentry/node@5.15.5": + version "5.15.5" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.15.5.tgz#f64cfcf8770cc0249f48b3ef439a7efcabdcec1d" + integrity sha512-BK0iTOiiIM0UnydLeT/uUBY1o1Sp85aqwaQRMfZbjMCsgXERLNGvzzV68FDH1cyC1nR6dREK3Gs8bxS4S54aLQ== + dependencies: + "@sentry/apm" "5.15.5" + "@sentry/core" "5.15.5" + "@sentry/hub" "5.15.5" + "@sentry/types" "5.15.5" + "@sentry/utils" "5.15.5" + cookie "^0.3.1" + https-proxy-agent "^4.0.0" + lru_map "^0.3.3" + tslib "^1.9.3" + +"@sentry/types@5.15.4", "@sentry/types@5.15.5": version "5.15.4" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.15.4.tgz#37f30e35b06e8e12ad1101f1beec3e9b88ca1aab" integrity sha512-quPHPpeAuwID48HLPmqBiyXE3xEiZLZ5D3CEbU3c3YuvvAg8qmfOOTI6z4Z3Eedi7flvYpnx3n7N3dXIEz30Eg== @@ -5231,6 +5297,14 @@ "@sentry/types" "5.15.4" tslib "^1.9.3" +"@sentry/utils@5.15.5": + version "5.15.5" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.15.5.tgz#dec1d4c79037c4da08b386f5d34409234dcbfb15" + integrity sha512-Nl9gl/MGnzSkuKeo3QaefoD/OJrFLB8HmwQ7HUbTXb6E7yyEzNKAQMHXGkwNAjbdYyYbd42iABP6Y5F/h39NtA== + dependencies: + "@sentry/types" "5.15.5" + tslib "^1.9.3" + "@sheerun/mutationobserver-shim@0.3.3": version "0.3.3" resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz#5405ee8e444ed212db44e79351f0c70a582aae25" @@ -5954,6 +6028,11 @@ resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.0.33.tgz#1073c4bc824754ae3d10cfab88ab0237ba964e4d" integrity sha1-EHPEvIJHVK49EM+riKsCN7qWTk0= +"@types/triple-beam@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.0.tgz#56d9a2d9e4fbd7bc363825d2521868537e4606ee" + integrity sha512-tl34wMtk3q+fSdRSJ+N83f47IyXLXPPuLjHm7cmAx0fE2Wml2TZCQV3FmQdSR5J6UEGV3qafG054e0cVVFCqPA== + "@types/uglify-js@*": version "3.9.0" resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.9.0.tgz#4490a140ca82aa855ad68093829e7fd6ae94ea87" @@ -9520,7 +9599,7 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.3.1: +cookie@0.3.1, cookie@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= @@ -12141,6 +12220,14 @@ express-unless@^0.3.0: resolved "https://registry.yarnpkg.com/express-unless/-/express-unless-0.3.1.tgz#2557c146e75beb903e2d247f9b5ba01452696e20" integrity sha1-JVfBRudb65A+LSR/m1ugFFJpbiA= +express-winston@4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/express-winston/-/express-winston-4.0.3.tgz#7160ddc6eb8efaa1bfc95a246124b05286096699" + integrity sha512-qzLLaTYAhajzfbR1d/hKT+N4kEoqDXC9wBqth0ygPg+DrM4QRipXDHLkIkaKSeiQ1L/ulezrT+T7lrKS+OcT7g== + dependencies: + chalk "^2.4.1" + lodash "^4.17.15" + express@4.17.1, express@^4.16.3, express@^4.16.4, express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -12366,7 +12453,7 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fast-safe-stringify@^2.0.4: +fast-safe-stringify@2.0.7, fast-safe-stringify@^2.0.4: version "2.0.7" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== @@ -18569,7 +18656,7 @@ logalot@^2.0.0, logalot@^2.1.0: figures "^1.3.5" squeak "^1.0.0" -logform@^2.1.1: +logform@2.1.2, logform@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/logform/-/logform-2.1.2.tgz#957155ebeb67a13164069825ce67ddb5bb2dd360" integrity sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ== @@ -18718,6 +18805,11 @@ lru-memoizer@^2.0.1: lodash.clonedeep "^4.5.0" lru-cache "~4.0.0" +lru_map@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" + integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0= + ltgt@^2.1.2: version "2.2.1" resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5" @@ -26720,7 +26812,7 @@ trim@0.0.1: resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= -triple-beam@^1.2.0, triple-beam@^1.3.0: +triple-beam@1.3.0, 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" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== @@ -28155,7 +28247,7 @@ winston-transport@^4.3.0: readable-stream "^2.3.6" triple-beam "^1.2.0" -winston@^3.0.0: +winston@3.2.1, winston@^3.0.0: version "3.2.1" resolved "https://registry.yarnpkg.com/winston/-/winston-3.2.1.tgz#63061377976c73584028be2490a1846055f77f07" integrity sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==