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
+
+
+
+
+
+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==