diff --git a/acceptance/extension-logging-fluentd/package-lock.json b/acceptance/extension-logging-fluentd/package-lock.json index 87a828e78fbb..a0bd497f7d8d 100644 --- a/acceptance/extension-logging-fluentd/package-lock.json +++ b/acceptance/extension-logging-fluentd/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@types/node": { - "version": "10.17.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.5.tgz", - "integrity": "sha512-RElZIr/7JreF1eY6oD5RF3kpmdcreuQPjg5ri4oQ5g9sq7YWU8HkfB3eH8GwAwxf5OaCh0VPi7r4N/yoTGelrA==", + "version": "10.17.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", + "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", "dev": true }, "JSONStream": { @@ -215,9 +215,9 @@ } }, "execa": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-3.3.0.tgz", - "integrity": "sha512-j5Vit5WZR/cbHlqU97+qcnw9WHRCIL4V1SVe75VcHcD1JRBdt8fv0zw89b7CQHQdUHTt2VjuhcF5ibAgVOxqpg==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", + "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -230,14 +230,6 @@ "p-finally": "^2.0.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" - }, - "dependencies": { - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - } } }, "fs-constants": { @@ -273,6 +265,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -331,9 +329,9 @@ "dev": true }, "npm-run-path": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.0.tgz", - "integrity": "sha512-8eyAOAH+bYXFPSnNnKr3J+yoybe8O87Is5rtAQ8qRczJz1ajcsjg8l2oZqP+Ppx15Ii3S1vUTjQN2h4YO2tWWQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { "path-key": "^3.0.0" @@ -364,9 +362,9 @@ "dev": true }, "path-key": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.0.tgz", - "integrity": "sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "process-nextick-args": { @@ -386,9 +384,9 @@ } }, "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==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -520,9 +518,9 @@ } }, "testcontainers": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-2.1.0.tgz", - "integrity": "sha512-c/q4XTxZ8wozovbadnyHwnvCMuGuCmi0nNEMkkC5AC1xCC62FP0JKgDiovZsOwOCXkG/DbK13uxd5awZifPRHw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-2.2.0.tgz", + "integrity": "sha512-SFj2LVj+MWA7YNQ2YS+istdW2P+NSZLHeVFH6Lsp4h8ZQy+fH+lEk7JXcNLt8j54M6zUDXzmQLTSCRpuCZ9v7Q==", "dev": true, "requires": { "byline": "^5.0.0", @@ -560,9 +558,9 @@ "dev": true }, "which": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.1.tgz", - "integrity": "sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" diff --git a/acceptance/extension-logging-fluentd/src/__tests__/accpetance/fluent.acceptance.ts b/acceptance/extension-logging-fluentd/src/__tests__/accpetance/fluent.acceptance.ts index a4c0a47fe8ca..15e22ae97cb7 100644 --- a/acceptance/extension-logging-fluentd/src/__tests__/accpetance/fluent.acceptance.ts +++ b/acceptance/extension-logging-fluentd/src/__tests__/accpetance/fluent.acceptance.ts @@ -57,8 +57,8 @@ describe('LoggingComponent', () => { async function givenAppWithCustomConfig() { app = givenApplication(); app.configure(LoggingBindings.FLUENT_SENDER).to({ - host: process.env.FLUENTD_SERVICE_HOST || '127.0.0.1', - port: +(process.env.FLUENTD_SERVICE_PORT_TCP || 0) || 24224, + host: process.env.FLUENTD_SERVICE_HOST ?? '127.0.0.1', + port: +(process.env.FLUENTD_SERVICE_PORT_TCP ?? 24224), timeout: 3.0, reconnectInterval: 600000, // 10 minutes }); diff --git a/acceptance/extension-logging-fluentd/src/__tests__/fixtures/fluentd.docker.ts b/acceptance/extension-logging-fluentd/src/__tests__/fixtures/fluentd.docker.ts index 5ccbcf5ede9e..0e8f5446cd2e 100644 --- a/acceptance/extension-logging-fluentd/src/__tests__/fixtures/fluentd.docker.ts +++ b/acceptance/extension-logging-fluentd/src/__tests__/fixtures/fluentd.docker.ts @@ -10,7 +10,6 @@ import {GenericContainer, StartedTestContainer} from 'testcontainers'; export const ROOT_DIR = path.join(__dirname, '../../../fixtures'); export const ETC_DIR = path.join(ROOT_DIR, 'etc'); -/* eslint-disable require-atomic-updates */ async function startFluentd() { if (process.env.FLUENTD_SERVICE_HOST != null) return; const container = await new GenericContainer( diff --git a/extensions/logging/README.md b/extensions/logging/README.md index 697a1f5552a4..ffb11eca310b 100644 --- a/extensions/logging/README.md +++ b/extensions/logging/README.md @@ -11,6 +11,10 @@ This module contains a component provides logging facilities based on > using `0.x.y` versions. Their APIs and functionality may be subject to > breaking changes in future releases. +## Architecture overview + +![logging-component](logging-component.png) + ## Installation ```sh @@ -34,21 +38,67 @@ In the constructor, add the component to your application: this.component(LoggingComponent); ``` -The component contributes bindings with keys listed below: +Now your application can add a controller as follows to leverage the logging +facilities: + +```ts +import {inject} from '@loopback/context'; +import {Logger, logInvocation} from '@loopback/extension-logging'; +import {get, param} from '@loopback/rest'; + +class MyController { + // Inject a winston logger + @inject(LoggingBindings.WINSTON_LOGGER) + private logger: Logger; + + // http access is logged by a global interceptor + @get('/greet/{name}') + // log the `greet` method invocations + @logInvocation() + greet(@param.path.string('name') name: string) { + return `Hello, ${name}`; + } + + @get('/hello/{name}') + hello(@param.path.string('name') name: string) { + // Use the winston logger explicitly + this.logger.log('info', `greeting ${name}`); + return `Hello, ${name}`; + } +} +``` + +## Configure the logging component + +The logging component can be configured as follows: + +```ts +app.configure(LoggingBindings.COMPONENT).to({ + enableFluent: false, // default to true + enableHttpAccessLog: true, // default to true +}); +``` + +The component contributes bindings with keys declared in `LoggingBindings` +namespace below: -- LoggingBindings.FLUENT_SENDER - A fluent sender -- LoggingBindings.WINSTON_LOGGER - A winston logger -- LoggingBindings.WINSTON_TRANSPORT_FLUENT - A fluent transport for winston +- FLUENT_SENDER - A fluent sender +- WINSTON_LOGGER - A winston logger +- WINSTON_TRANSPORT_FLUENT - A fluent transport for winston +- WINSTON_INTERCEPTOR - A local interceptor set by `@logInvocation` to log + method invocations +- WINSTON_HTTP_ACCESS_LOGGER - A global interceptor that logs http access with + [Morgan](https://github.com/expressjs/morgan) format The fluent sender and transport for winston can be configured against -`LoggingBindings.FLUENT_SENDER`: +`FLUENT_SENDER`: ```ts import {LoggingBindings} from '@loopback/extension-logging'; app.configure(LoggingBindings.FLUENT_SENDER).to({ - host: process.env.FLUENTD_SERVICE_HOST || 'localhost', - port: +(process.env.FLUENTD_SERVICE_PORT_TCP || 0) || 24224, + host: process.env.FLUENTD_SERVICE_HOST ?? 'localhost', + port: +(process.env.FLUENTD_SERVICE_PORT_TCP ?? 24224), timeout: 3.0, reconnectInterval: 600000, // 10 minutes }); @@ -75,9 +125,17 @@ points: ```ts import {extensionFor} from '@loopback/core'; import {format} from 'winston'; -import {WINSTON_FORMAT, WINSTON_TRANSPORT} from '@loopback/extension-logging'; - -const myFormat: Format = ...; +import { + WINSTON_FORMAT, + WINSTON_TRANSPORT, + WinstonFormat, + WinstonTransports, +} from '@loopback/extension-logging'; + +const myFormat: WinstonFormat = format((info, opts) => { + console.log(info); + return false; +})(); ctx .bind('logging.winston.formats.myFormat') @@ -87,6 +145,27 @@ ctx .bind('logging.winston.formats.colorize') .to(format.colorize()) .apply(extensionFor(WINSTON_FORMAT)); + +const consoleTransport = new WinstonTransports.Console({ + level: 'info', + format: format.combine(format.colorize(), format.simple()), +}); +ctx + .bind('logging.winston.transports.console') + .to(consoleTransport) + .apply(extensionFor(WINSTON_TRANSPORT)); +``` + +If no transport is contributed, the winston logger uses the +[console](https://github.com/winstonjs/winston/blob/master/docs/transports.md#console-transport). + +The access log interceptor can also be configured to customize +[Morgan format and options](https://github.com/expressjs/morgan#morganformat-options): + +```ts +ctx + .configure(LoggingBindings.WINSTON_HTTP_ACCESS_LOGGER) + .to({format: 'combined'}); ``` ## Contributions diff --git a/extensions/logging/logging-component.png b/extensions/logging/logging-component.png new file mode 100644 index 000000000000..6f856e3c7aba Binary files /dev/null and b/extensions/logging/logging-component.png differ diff --git a/extensions/logging/package-lock.json b/extensions/logging/package-lock.json index 5edea88a0ef9..39d5725f3439 100644 --- a/extensions/logging/package-lock.json +++ b/extensions/logging/package-lock.json @@ -4,127 +4,98 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@types/node": { - "version": "10.17.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.5.tgz", - "integrity": "sha512-RElZIr/7JreF1eY6oD5RF3kpmdcreuQPjg5ri4oQ5g9sq7YWU8HkfB3eH8GwAwxf5OaCh0VPi7r4N/yoTGelrA==", - "dev": true - }, - "JSONStream": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", - "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", + "@types/body-parser": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", + "integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==", "dev": true, "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" + "@types/connect": "*", + "@types/node": "*" } }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", - "dev": true - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "dev": true, "requires": { - "lodash": "^4.17.14" + "@types/node": "*" } }, - "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "@types/express": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz", + "integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==", "dev": true, "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - }, - "dependencies": { - "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==", - "dev": true, - "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" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "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==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "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==", - "dev": true - } - } - } + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" } }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "@types/express-serve-static-core": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.1.tgz", + "integrity": "sha512-9e7jj549ZI+RxY21Cl0t8uBnWyb22HzILupyHZjYEVK//5TT/1bZodU+yUbLnPdoYViBBnNWbxp4zYjGV0zUGw==", "dev": true, "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" + "@types/node": "*", + "@types/range-parser": "*" } }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "@types/mime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", "dev": true }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "dev": true + "@types/morgan": { + "version": "1.7.37", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.7.37.tgz", + "integrity": "sha512-tIdEA10BcHcOumMmUiiYdw8lhiVVq62r0ghih5Xpp4WETkfsMiTUZL4w9jCI502BBOrKhFrAOGml9IeELvVaBA==", + "dev": true, + "requires": { + "@types/express": "*" + } }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "@types/node": { + "version": "10.17.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", + "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", "dev": true }, - "byline": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", - "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=", + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", "dev": true }, - "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", - "dev": true + "@types/serve-static": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", + "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, + "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" + } + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } }, "color": { "version": "3.0.0", @@ -176,83 +147,23 @@ "text-hex": "1.0.x" } }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "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==", - "dev": true, - "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==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "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=" }, - "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, - "default-gateway": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-5.0.5.tgz", - "integrity": "sha512-z2RnruVmj8hVMmAnEJMTIJNijhKCDiGjbLP+BHJFOT7ld3Bo5qcIBpVYDniqhbMIIf+jZDlkP2MkPXiQy/DBLA==", - "dev": true, - "requires": { - "execa": "^3.3.0" - } + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "diagnostics": { "version": "1.1.1", @@ -264,87 +175,10 @@ "kuler": "1.0.x" } }, - "docker-modem": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.9.tgz", - "integrity": "sha512-lVjqCSCIAUDZPAZIeyM125HXfNvOmYYInciphNrLrylUtKyW66meAjSPXWchKVzoIYZx69TPnAepVSSkeawoIw==", - "dev": true, - "requires": { - "JSONStream": "1.3.2", - "debug": "^3.2.6", - "readable-stream": "~1.0.26-4", - "split-ca": "^1.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "dockerode": { - "version": "2.5.8", - "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.5.8.tgz", - "integrity": "sha512-+7iOUYBeDTScmOmQqpUYQaE7F4vvIt6+gIZNHWhqAQEI887tiPFB9OvXI/HzQYqfUNvukMK+9myLW63oTJPZpw==", - "dev": true, - "requires": { - "concat-stream": "~1.6.2", - "docker-modem": "^1.0.8", - "tar-fs": "~1.16.3" - }, - "dependencies": { - "pump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "tar-fs": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", - "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", - "dev": true, - "requires": { - "chownr": "^1.0.1", - "mkdirp": "^0.5.1", - "pump": "^1.0.0", - "tar-stream": "^1.1.2" - } - } - } + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "enabled": { "version": "1.0.2", @@ -354,15 +188,6 @@ "env-variable": "0.0.x" } }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, "env-variable": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", @@ -373,38 +198,6 @@ "resolved": "https://registry.npmjs.org/event-lite/-/event-lite-0.1.2.tgz", "integrity": "sha512-HnSYx1BsJ87/p6swwzv+2v6B4X+uxUteoDfRxsAb1S1BePzQqOLevVmkdA15GHJVd9A9Ok6wygUR18Hu0YeV9g==" }, - "execa": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-3.3.0.tgz", - "integrity": "sha512-j5Vit5WZR/cbHlqU97+qcnw9WHRCIL4V1SVe75VcHcD1JRBdt8fv0zw89b7CQHQdUHTt2VjuhcF5ibAgVOxqpg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "p-finally": "^2.0.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "dependencies": { - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "p-finally": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", - "dev": true - } - } - }, "fast-safe-stringify": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", @@ -416,40 +209,13 @@ "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" }, "fluent-logger": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fluent-logger/-/fluent-logger-3.3.1.tgz", - "integrity": "sha512-hlLHXS+eJ32dlxEW4xexhjmZKb9/rAPL9suBVRe1uyHGM8QPk+EQe6l4cGlulhILMXTyAXGtFamyzWlHoRqbeQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/fluent-logger/-/fluent-logger-3.4.1.tgz", + "integrity": "sha512-lERIhXAvhtCYeQq8K7sBDg/HY9GkiVRq5xY3oN+hcSINVKwqwBzG6LQOJK73EnV50qO59U7XEmRnn2hBzLWaHw==", "requires": { "msgpack-lite": "*" } }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, - "get-port": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz", - "integrity": "sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==", - "dev": true - }, - "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true - }, "ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", @@ -480,18 +246,6 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - }, "kuler": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", @@ -515,39 +269,31 @@ "fecha": "^2.3.3", "ms": "^2.1.1", "triple-beam": "^1.3.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } } }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, + "morgan": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", "requires": { - "minimist": "0.0.8" + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "msgpack-lite": { "version": "0.1.26", @@ -560,44 +306,24 @@ "isarray": "^1.0.0" } }, - "node-duration": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/node-duration/-/node-duration-1.0.4.tgz", - "integrity": "sha512-eUXYNSY7DL53vqfTosggWkvyIW3bhAcqBDIlolgNYlZhianXTrCL50rlUJWD1eRqkIxMppXTfiFbp+9SjpPrgA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.0.tgz", - "integrity": "sha512-8eyAOAH+bYXFPSnNnKr3J+yoybe8O87Is5rtAQ8qRczJz1ajcsjg8l2oZqP+Ppx15Ii3S1vUTjQN2h4YO2tWWQ==", - "dev": true, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", "requires": { - "path-key": "^3.0.0" + "ee-first": "1.1.1" } }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" }, "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", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, "p-event": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.1.0.tgz", @@ -622,27 +348,11 @@ "p-finally": "^1.0.0" } }, - "path-key": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.0.tgz", - "integrity": "sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg==", - "dev": true - }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "readable-stream": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", @@ -654,30 +364,9 @@ } }, "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "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", @@ -687,181 +376,41 @@ "is-arrayish": "^0.3.1" } }, - "split-ca": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", - "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=", - "dev": true - }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" }, - "stream-to-array": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz", - "integrity": "sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M=", - "dev": true, - "requires": { - "any-promise": "^1.1.0" - } - }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "requires": { "safe-buffer": "~5.2.0" - } - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "tar-fs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz", - "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==", - "dev": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp": "^0.5.1", - "pump": "^3.0.0", - "tar-stream": "^2.0.0" }, "dependencies": { - "bl": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.0.tgz", - "integrity": "sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==", - "dev": true, - "requires": { - "readable-stream": "^3.0.1" - } - }, - "tar-stream": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.0.tgz", - "integrity": "sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw==", - "dev": true, - "requires": { - "bl": "^3.0.0", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - } - } - }, - "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", - "dev": true, - "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", - "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" - }, - "dependencies": { - "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==", - "dev": true, - "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==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" } } }, - "testcontainers": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-2.1.0.tgz", - "integrity": "sha512-c/q4XTxZ8wozovbadnyHwnvCMuGuCmi0nNEMkkC5AC1xCC62FP0JKgDiovZsOwOCXkG/DbK13uxd5awZifPRHw==", - "dev": true, - "requires": { - "byline": "^5.0.0", - "debug": "^4.1.1", - "default-gateway": "^5.0.2", - "dockerode": "^2.5.8", - "get-port": "^4.2.0", - "node-duration": "^1.0.4", - "stream-to-array": "^2.3.0", - "tar-fs": "^2.0.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==" }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", - "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==" }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "which": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.1.tgz", - "integrity": "sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, "winston": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", @@ -888,9 +437,9 @@ }, "dependencies": { "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==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -901,11 +450,6 @@ "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==" - }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -915,18 +459,6 @@ } } } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true } } } diff --git a/extensions/logging/package.json b/extensions/logging/package.json index 615f8dca51d1..34c5eede3f4c 100644 --- a/extensions/logging/package.json +++ b/extensions/logging/package.json @@ -16,18 +16,20 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/context": "^1.23.3", - "@loopback/core": "^1.10.5", - "@loopback/rest": "^1.21.0", - "fluent-logger": "^3.3.1", + "@loopback/context": "^1.24.0", + "@loopback/core": "^1.11.0", + "@loopback/rest": "^1.24.0", + "fluent-logger": "^3.4.1", + "morgan": "^1.9.1", "winston": "^3.2.1", "winston-transport": "^4.3.0" }, "devDependencies": { - "@loopback/build": "^2.0.14", - "@loopback/eslint-config": "^4.1.2", - "@loopback/testlab": "^1.9.2", - "@types/node": "^10.17.5", + "@loopback/build": "^3.0.1", + "@loopback/eslint-config": "^5.0.1", + "@loopback/testlab": "^1.9.5", + "@types/morgan": "^1.7.37", + "@types/node": "^10.17.13", "p-event": "^4.1.0" }, "keywords": [ diff --git a/extensions/logging/src/__tests__/accpetance/logging.component.acceptance.ts b/extensions/logging/src/__tests__/accpetance/logging.component.acceptance.ts index f17574159fc6..3bcfa356faf3 100644 --- a/extensions/logging/src/__tests__/accpetance/logging.component.acceptance.ts +++ b/extensions/logging/src/__tests__/accpetance/logging.component.acceptance.ts @@ -45,3 +45,42 @@ describe('LoggingComponent', () => { return new Application(); } }); + +describe('LoggingComponent without fluent', () => { + let app: Application; + + before(givenAppAndLoggingComponent); + + it('does not bind a fluent sender', async () => { + expect(app.isBound(LoggingBindings.FLUENT_SENDER)).to.be.false(); + }); + + it('binds a winston logger', async () => { + expect(app.isBound(LoggingBindings.WINSTON_LOGGER)).to.be.true(); + const logger = await app.get(LoggingBindings.WINSTON_LOGGER); + expect(logger.log).to.be.Function(); + }); + + it('does not bind a winston transport for fluent', async () => { + expect(app.isBound(LoggingBindings.WINSTON_TRANSPORT_FLUENT)).to.be.false(); + }); + + it('does not bind a winston access log', async () => { + expect( + app.isBound(LoggingBindings.WINSTON_HTTP_ACCESS_LOGGER), + ).to.be.false(); + }); + + async function givenAppAndLoggingComponent() { + app = givenApplication(); + app.configure(LoggingBindings.COMPONENT).to({ + enableFluent: false, + enableHttpAccessLog: false, + }); + app.component(LoggingComponent); + } + + function givenApplication() { + return new Application(); + } +}); diff --git a/extensions/logging/src/__tests__/accpetance/logging.interceptor.acceptance.ts b/extensions/logging/src/__tests__/accpetance/logging.interceptor.acceptance.ts new file mode 100644 index 000000000000..673cd1dbf4d4 --- /dev/null +++ b/extensions/logging/src/__tests__/accpetance/logging.interceptor.acceptance.ts @@ -0,0 +1,112 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/extension-health +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {extensionFor} from '@loopback/core'; +import {get, param, RestApplication, RestServerConfig} from '@loopback/rest'; +import { + Client, + createRestAppClient, + expect, + givenHttpServerConfig, +} from '@loopback/testlab'; +import {format, transports} from 'winston'; +import { + logInvocation, + LoggingComponent, + WinstonFormat, + WinstonLogRecord, + WINSTON_FORMAT, + WINSTON_TRANSPORT, +} from '../..'; +import {LoggingBindings} from '../../keys'; + +describe('Logging interceptor', () => { + let app: RestApplication; + let request: Client; + let logs: WinstonLogRecord[] = []; + + class MyController { + @get('/greet/{name}') + @logInvocation() + greet(@param.path.string('name') name: string) { + return `Hello, ${name}`; + } + + @get('/hello/{name}') + hello(@param.path.string('name') name: string) { + return `Hello, ${name}`; + } + } + + before(async () => { + app = givenRestApplication(); + app.controller(MyController); + logs = []; + const myFormat: WinstonFormat = format((info, opts) => { + logs.push(info); + return false; + })(); + app + .bind('logging.winston.formats.myFormat') + .to(myFormat) + .apply(extensionFor(WINSTON_FORMAT)); + const consoleTransport = new transports.Console({ + level: 'verbose', + format: myFormat, + }); + app + .bind('logging.winston.transports.console') + .to(consoleTransport) + .apply(extensionFor(WINSTON_TRANSPORT)); + app.configure(LoggingBindings.COMPONENT).to({ + enableFluent: false, + }); + app.component(LoggingComponent); + await app.start(); + request = createRestAppClient(app); + }); + + after(async () => { + if (app) await app.stop(); + (app as unknown) = undefined; + }); + + beforeEach(() => { + logs = []; + }); + + it('logs http req/res for /hello', async () => { + await request.get('/hello/John').expect(200); + expect(logs.length).to.equal(1); + expect(logs[0].level).to.equal('info'); + expect(logs[0].message).to.match(/"GET \/hello\/John HTTP\/1.1" 200/); + }); + + it('logs http req/res for /greet', async () => { + await request.get('/greet/John').expect(200); + expect(logs.length).to.equal(3); + expect(logs[0].level).to.equal('verbose'); + expect(logs[1].level).to.equal('verbose'); + expect(logs[2].level).to.equal('info'); + expect(logs[2].message).to.match(/"GET \/greet\/John HTTP\/1.1" 200/); + }); + + it('logs method invocation of greet()', async () => { + await request.get('/greet/Jane').expect(200); + expect(logs).to.containEql({ + level: 'verbose', + message: "invoking MyController.prototype.greet with: [ 'Jane' ]", + }); + expect(logs).to.containEql({ + level: 'verbose', + message: 'returned from MyController.prototype.greet: Hello, Jane', + }); + }); + + function givenRestApplication(config?: RestServerConfig) { + const rest = Object.assign({}, givenHttpServerConfig(), config); + return new RestApplication({rest}); + } +}); diff --git a/extensions/logging/src/__tests__/accpetance/winston.acceptance.ts b/extensions/logging/src/__tests__/accpetance/winston.acceptance.ts index 3407c5f22a6c..fb3e3b67729c 100644 --- a/extensions/logging/src/__tests__/accpetance/winston.acceptance.ts +++ b/extensions/logging/src/__tests__/accpetance/winston.acceptance.ts @@ -6,12 +6,14 @@ import {Context} from '@loopback/context'; import {extensionFor} from '@loopback/core'; import {expect} from '@loopback/testlab'; -import {format, LoggerOptions, transports} from 'winston'; import { - Format, + format, LoggingBindings, - TransformableInfo, + WinstonFormat, + WinstonLoggerOptions, WinstonLoggerProvider, + WinstonLogRecord, + WinstonTransports, WINSTON_FORMAT, WINSTON_TRANSPORT, } from '../..'; @@ -23,7 +25,7 @@ describe('Winston Logger', () => { it('creates a winston logger', async () => { ctx.bind(LoggingBindings.WINSTON_LOGGER).toProvider(WinstonLoggerProvider); - ctx.configure(LoggingBindings.WINSTON_LOGGER).to({ + ctx.configure(LoggingBindings.WINSTON_LOGGER).to({ level: 'info', format: format.json(), defaultMeta: {framework: 'LoopBack'}, @@ -34,12 +36,12 @@ describe('Winston Logger', () => { it('creates a winston logger with transports', async () => { ctx.bind(LoggingBindings.WINSTON_LOGGER).toProvider(WinstonLoggerProvider); - ctx.configure(LoggingBindings.WINSTON_LOGGER).to({ + ctx.configure(LoggingBindings.WINSTON_LOGGER).to({ level: 'info', format: format.json(), defaultMeta: {framework: 'LoopBack'}, }); - const consoleTransport = new transports.Console({ + const consoleTransport = new WinstonTransports.Console({ level: 'info', format: format.combine(format.colorize(), format.simple()), }); @@ -53,12 +55,12 @@ describe('Winston Logger', () => { it('creates a winston logger with formats', async () => { ctx.bind(LoggingBindings.WINSTON_LOGGER).toProvider(WinstonLoggerProvider); - ctx.configure(LoggingBindings.WINSTON_LOGGER).to({ + ctx.configure(LoggingBindings.WINSTON_LOGGER).to({ level: 'info', defaultMeta: {framework: 'LoopBack'}, }); - const logs: TransformableInfo[] = []; - const myFormat: Format = format((info, opts) => { + const logs: WinstonLogRecord[] = []; + const myFormat: WinstonFormat = format((info, opts) => { logs.push(info); return false; })(); diff --git a/extensions/logging/src/decorators/index.ts b/extensions/logging/src/decorators/index.ts new file mode 100644 index 000000000000..61992178bdf2 --- /dev/null +++ b/extensions/logging/src/decorators/index.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/extension-logging +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './logging.decorator'; diff --git a/extensions/logging/src/decorators/logging.decorator.ts b/extensions/logging/src/decorators/logging.decorator.ts new file mode 100644 index 000000000000..3d2d0a65b720 --- /dev/null +++ b/extensions/logging/src/decorators/logging.decorator.ts @@ -0,0 +1,28 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/extension-logging +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {intercept} from '@loopback/context'; +import {LoggingBindings} from '../keys'; + +/** + * @logInvocation decorator for method invocations. + * + * @example + * ```ts + * import {logInvocation} from '@loopback/extension-logging'; + * + * export class HelloController { + * @logInvocation() + * hello(name: string) { + * return `Hello, ${name}`; + * } + * } + * ``` + */ +export function logInvocation() { + // A shortcut to `@intercept` that invokes the winston interceptor that logs + // method invocations + return intercept(LoggingBindings.WINSTON_INVOCATION_LOGGER); +} diff --git a/extensions/logging/src/fluent.ts b/extensions/logging/src/fluent.ts index 3daa142501a0..8af73668f4b0 100644 --- a/extensions/logging/src/fluent.ts +++ b/extensions/logging/src/fluent.ts @@ -9,6 +9,8 @@ import {LoggingBindings} from './keys'; const fluent = require('fluent-logger'); import TransportStream = require('winston-transport'); +export {FluentSender} from 'fluent-logger'; + /** * Provider for FluentSender */ @@ -51,7 +53,7 @@ export class FluentTransportProvider implements Provider { `Fluent is not configured. Please configure ${this.binding.key}.`, ); } - const cls = fluent.support.winstonTransport(); - return new cls('LoopBack', options); + const winstonTransportClass = fluent.support.winstonTransport(); + return new winstonTransportClass('LoopBack', options); } } diff --git a/extensions/logging/src/index.ts b/extensions/logging/src/index.ts index 4a3a4ef8a185..f619551c2239 100644 --- a/extensions/logging/src/index.ts +++ b/extensions/logging/src/index.ts @@ -3,6 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +export * from './decorators'; export * from './keys'; export * from './logging.component'; export * from './winston'; diff --git a/extensions/logging/src/interceptors/index.ts b/extensions/logging/src/interceptors/index.ts new file mode 100644 index 000000000000..ea923a19ef1c --- /dev/null +++ b/extensions/logging/src/interceptors/index.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/extension-logging +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './logging.interceptor'; diff --git a/extensions/logging/src/interceptors/logging.interceptor.ts b/extensions/logging/src/interceptors/logging.interceptor.ts new file mode 100644 index 000000000000..63096f3e28d7 --- /dev/null +++ b/extensions/logging/src/interceptors/logging.interceptor.ts @@ -0,0 +1,127 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/extension-logging +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + asGlobalInterceptor, + bind, + BindingScope, + config, + ContextTags, + inject, + Interceptor, + InvocationContext, + Provider, + ValueOrPromise, +} from '@loopback/context'; +import {RequestContext, RestBindings} from '@loopback/rest'; +import morgan from 'morgan'; +import {format} from 'util'; +import {Logger} from 'winston'; +import {LoggingBindings} from '../keys'; + +/** + * A local interceptor that provides logging for method invocations. + */ +@bind({ + tags: {[ContextTags.KEY]: LoggingBindings.WINSTON_INVOCATION_LOGGER}, + scope: BindingScope.SINGLETON, +}) +export class InvocationLoggingInterceptor implements Provider { + constructor( + @inject(LoggingBindings.WINSTON_LOGGER) + private logger: Logger, + ) {} + + value() { + return this.intercept.bind(this); + } + + async intercept( + invocationCtx: InvocationContext, + next: () => ValueOrPromise, + ) { + try { + this.logger.log( + 'verbose', + format( + 'invoking %s with:', + invocationCtx.targetName, + invocationCtx.args, + ), + ); + const result = await next(); + this.logger.log( + 'verbose', + format('returned from %s:', invocationCtx.targetName, result), + ); + return result; + } catch (err) { + this.logger.log( + 'error', + format('error from %s', invocationCtx.targetName, err), + ); + throw err; + } + } +} + +export interface AccessLogOptions extends morgan.Options { + format?: string | morgan.FormatFn; +} + +/** + * A global interceptor that provides logging for http requests/responses. + */ +@bind(asGlobalInterceptor('logging'), { + tags: { + [ContextTags.KEY]: LoggingBindings.WINSTON_HTTP_ACCESS_LOGGER, + // Only apply to invocations from REST routes + [ContextTags.GLOBAL_INTERCEPTOR_SOURCE]: 'route', + }, + scope: BindingScope.SINGLETON, +}) +export class HttpAccessLogInterceptor implements Provider { + constructor( + @inject(LoggingBindings.WINSTON_LOGGER) + private logger: Logger, + @config() + private morganOptions: AccessLogOptions = {format: 'combined'}, + ) {} + + value() { + return this.intercept.bind(this); + } + + async intercept( + invocationCtx: InvocationContext, + next: () => ValueOrPromise, + ) { + const reqCtx = await invocationCtx.get( + RestBindings.Http.CONTEXT, + ); + const options: AccessLogOptions = { + ...this.morganOptions, + stream: { + write: (message: string) => { + this.logger.info(message); + }, + }, + }; + if (typeof options.format === 'function') { + morgan(options.format, options)( + reqCtx.request, + reqCtx.response, + () => {}, + ); + } else { + morgan(options.format ?? 'combined', options)( + reqCtx.request, + reqCtx.response, + () => {}, + ); + } + return next(); + } +} diff --git a/extensions/logging/src/keys.ts b/extensions/logging/src/keys.ts index 4ea612c897c4..cb9f0cd15f0b 100644 --- a/extensions/logging/src/keys.ts +++ b/extensions/logging/src/keys.ts @@ -3,11 +3,11 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {BindingKey} from '@loopback/core'; +import {BindingKey, GenericInterceptor} from '@loopback/core'; import {FluentSender} from 'fluent-logger'; -import {Logger} from 'winston'; -import * as Transport from 'winston-transport'; +import * as WinstonTransport from 'winston-transport'; import {LoggingComponent} from './logging.component'; +import {WinstonLogger} from './winston'; /** * Binding keys used by this component. @@ -22,11 +22,31 @@ export namespace LoggingBindings { 'logging.fluent.sender', ); - export const WINSTON_LOGGER = BindingKey.create( + /** + * Binding key for winston logger + */ + export const WINSTON_LOGGER = BindingKey.create( 'logging.winston.logger', ); - export const WINSTON_TRANSPORT_FLUENT = BindingKey.create( + /** + * Binding key for winston transport backed by fluent + */ + export const WINSTON_TRANSPORT_FLUENT = BindingKey.create( 'logging.winston.transports.fluent', ); + + /** + * Binding key for method invocation logger with winston + */ + export const WINSTON_INVOCATION_LOGGER = BindingKey.create< + GenericInterceptor + >('logging.winston.invocationLogger'); + + /** + * Binding key for http access logger with winston + */ + export const WINSTON_HTTP_ACCESS_LOGGER = BindingKey.create< + GenericInterceptor + >('logging.winston.httpAccessLogger'); } diff --git a/extensions/logging/src/logging.component.ts b/extensions/logging/src/logging.component.ts index 28b34ef7b990..6d10a44eaf81 100644 --- a/extensions/logging/src/logging.component.ts +++ b/extensions/logging/src/logging.component.ts @@ -7,14 +7,33 @@ import { bind, Binding, Component, + config, ContextTags, extensionFor, ProviderMap, } from '@loopback/core'; import {FluentSenderProvider, FluentTransportProvider} from './fluent'; +import { + HttpAccessLogInterceptor, + InvocationLoggingInterceptor, +} from './interceptors'; import {LoggingBindings} from './keys'; import {WinstonLoggerProvider, WINSTON_TRANSPORT} from './winston'; +/** + * Configuration for LoggingComponent + */ +export type LoggingComponentConfig = { + /** + * A flag to enable fluent, default to `true` + */ + enableFluent?: boolean; + /** + * A flag to enable Winston-based http access log, default to `true` + */ + enableHttpAccessLog?: boolean; +}; + /** * A component providing logging facilities */ @@ -23,16 +42,35 @@ export class LoggingComponent implements Component { providers: ProviderMap; bindings: Binding[]; - constructor() { + constructor( + @config() + loggingConfig: LoggingComponentConfig | undefined, + ) { + loggingConfig = { + enableFluent: true, + enableHttpAccessLog: true, + ...loggingConfig, + }; this.providers = { - [LoggingBindings.FLUENT_SENDER.key]: FluentSenderProvider, [LoggingBindings.WINSTON_LOGGER.key]: WinstonLoggerProvider, + [LoggingBindings.WINSTON_INVOCATION_LOGGER + .key]: InvocationLoggingInterceptor, }; - this.bindings = [ - Binding.bind(LoggingBindings.WINSTON_TRANSPORT_FLUENT) - .toProvider(FluentTransportProvider) - .apply(extensionFor(WINSTON_TRANSPORT)), - ]; + if (loggingConfig.enableHttpAccessLog) { + this.providers[ + LoggingBindings.WINSTON_HTTP_ACCESS_LOGGER.key + ] = HttpAccessLogInterceptor; + } + + if (loggingConfig.enableFluent) { + this.providers[LoggingBindings.FLUENT_SENDER.key] = FluentSenderProvider; + // Only create fluent transport if it's configured + this.bindings = [ + Binding.bind(LoggingBindings.WINSTON_TRANSPORT_FLUENT) + .toProvider(FluentTransportProvider) + .apply(extensionFor(WINSTON_TRANSPORT)), + ]; + } } } diff --git a/extensions/logging/src/winston.ts b/extensions/logging/src/winston.ts index 9f46464d559b..5bcaf1a8a7fd 100644 --- a/extensions/logging/src/winston.ts +++ b/extensions/logging/src/winston.ts @@ -14,18 +14,48 @@ import { } from 'winston'; import * as Transport from 'winston-transport'; -export {Format, TransformableInfo} from 'logform'; +/** + * Re-export logform/winston types + */ +export { + Format as WinstonFormat, + TransformableInfo as WinstonLogRecord, +} from 'logform'; +export { + format, + Logger as WinstonLogger, + LoggerOptions as WinstonLoggerOptions, + transports as WinstonTransports, +} from 'winston'; +/** + * An extension point for winston transports + */ export const WINSTON_TRANSPORT = 'logging.winston.transport'; +/** + * An extension point for winston formats + */ export const WINSTON_FORMAT = 'logging.winston.format'; +/** + * A provider class that creates WinstonLogger instances + */ export class WinstonLoggerProvider implements Provider { constructor( + /** + * Getter for transports + */ @extensions(WINSTON_TRANSPORT) private transports: Getter, + /** + * Getter for formats + */ @extensions(WINSTON_FORMAT) private formats: Getter, + /** + * Configuration for the logger + */ @config() private options: LoggerOptions = {}, ) {}