diff --git a/examples/todo/package-lock.json b/examples/todo/package-lock.json index 28988c851659..8663c12f0a1d 100644 --- a/examples/todo/package-lock.json +++ b/examples/todo/package-lock.json @@ -30,18 +30,59 @@ "js-tokens": "^4.0.0" } }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@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": { + "@types/node": "*" + } + }, "@types/eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", "dev": true }, + "@types/express": { + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz", + "integrity": "sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.5.tgz", + "integrity": "sha512-578YH5Lt88AKoADy0b2jQGwJtrBxezXtVe/MBqWXKZpqx91SnC0pVkVCcxcytz3lWW+cHBYDi3Ysh0WXc+rAYw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/range-parser": "*" + } + }, "@types/json-schema": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", @@ -54,12 +95,49 @@ "integrity": "sha512-kMNLM5JBcasgYscD9x/Gvr6lTAv2NVgsKtet/hm93qMyf/D1pt+7jeEZklKJKxMVmXjxbRVQQGfqDSfipYCO6w==", "dev": true }, + "@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 + }, + "@types/morgan": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.0.tgz", + "integrity": "sha512-warrzirh5dlTMaETytBTKR886pRXwr+SMZD87ZE13gLMR8Pzz69SiYFkvoDaii78qGP1iyBIUYz5GiXyryO//A==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/node": { "version": "10.17.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.21.tgz", "integrity": "sha512-PQKsydPxYxF1DsAFWmunaxd3sOi3iMt6Zmx/tgaagHYmwJ/9cRH91hQkeJZaUGWbvn0K5HlSVEXkn5U/llWPpQ==", "dev": true }, + "@types/qs": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz", + "integrity": "sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw==", + "dev": true + }, + "@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 + }, + "@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": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "2.31.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.31.0.tgz", @@ -230,6 +308,21 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "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" + }, + "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==" + } + } + }, "bcp47": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/bcp47/-/bcp47-1.1.2.tgz", @@ -382,6 +475,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -400,6 +498,11 @@ "safer-buffer": "^2.1.0" } }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1148,6 +1251,33 @@ "minimist": "^1.2.5" } }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -1183,6 +1313,19 @@ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.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==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", diff --git a/examples/todo/package.json b/examples/todo/package.json index 9c6895c00978..bb4bf339b1d3 100644 --- a/examples/todo/package.json +++ b/examples/todo/package.json @@ -45,6 +45,7 @@ "@loopback/rest-explorer": "^2.1.2", "@loopback/service-proxy": "^2.1.2", "loopback-connector-rest": "^3.6.0", + "morgan": "^1.10.0", "tslib": "^1.11.1" }, "devDependencies": { @@ -56,6 +57,7 @@ "@types/node": "^10.17.21", "@typescript-eslint/eslint-plugin": "^2.31.0", "@typescript-eslint/parser": "^2.31.0", + "@types/morgan": "^1.9.0", "eslint": "^6.8.0", "eslint-config-prettier": "^6.11.0", "eslint-plugin-eslint-plugin": "^2.2.1", diff --git a/examples/todo/src/__tests__/acceptance/todo.acceptance.ts b/examples/todo/src/__tests__/acceptance/todo.acceptance.ts index 9c75e5dd6db4..d95fe1bc7a1a 100644 --- a/examples/todo/src/__tests__/acceptance/todo.acceptance.ts +++ b/examples/todo/src/__tests__/acceptance/todo.acceptance.ts @@ -11,6 +11,7 @@ import { givenHttpServerConfig, toJSON, } from '@loopback/testlab'; +import morgan from 'morgan'; import {TodoListApplication} from '../../application'; import {Todo} from '../../models/'; import {TodoRepository} from '../../repositories/'; @@ -184,6 +185,23 @@ describe('TodoApplication', () => { }); }); + context('allows logging to be reconfigured', () => { + it('logs http requests', async () => { + const logs: string[] = []; + const logToArray = (str: string) => { + logs.push(str); + }; + app.configure('middleware.morgan').to({ + stream: { + write: logToArray, + }, + }); + await client.get('/todos'); + expect(logs.length).to.eql(1); + expect(logs[0]).to.match(/"GET \/todos HTTP\/1\.1" 200 - "-"/); + }); + }); + it('queries todos with a filter', async () => { await givenTodoInstance({title: 'wake up', isComplete: true}); diff --git a/examples/todo/src/application.ts b/examples/todo/src/application.ts index 2290f971ce7a..eb883fc2246e 100644 --- a/examples/todo/src/application.ts +++ b/examples/todo/src/application.ts @@ -9,6 +9,7 @@ import {RepositoryMixin} from '@loopback/repository'; import {RestApplication} from '@loopback/rest'; import {RestExplorerComponent} from '@loopback/rest-explorer'; import {ServiceMixin} from '@loopback/service-proxy'; +import morgan from 'morgan'; import path from 'path'; import {MySequence} from './sequence'; @@ -36,5 +37,29 @@ export class TodoListApplication extends BootMixin( nested: true, }, }; + + this.setupLogging(); + } + + private setupLogging() { + // Register `morgan` express middleware + // Create a middleware factory wrapper for `morgan(format, options)` + const morganFactory = (config?: morgan.Options) => { + this.debug('Morgan configuration', config); + return morgan('combined', config); + }; + + // Print out logs using `debug` + const defaultConfig: morgan.Options = { + stream: { + write: str => { + this._debug(str); + }, + }, + }; + this.expressMiddleware(morganFactory, defaultConfig, { + injectConfiguration: 'watch', + key: 'middleware.morgan', + }); } } diff --git a/examples/todo/src/sequence.ts b/examples/todo/src/sequence.ts index 2cf45180d379..db85cb4d7d17 100644 --- a/examples/todo/src/sequence.ts +++ b/examples/todo/src/sequence.ts @@ -3,10 +3,11 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Context, inject} from '@loopback/context'; +import {inject} from '@loopback/context'; import { FindRoute, InvokeMethod, + InvokeMiddleware, ParseParams, Reject, RequestContext, @@ -18,8 +19,14 @@ import { const SequenceActions = RestBindings.SequenceActions; export class MySequence implements SequenceHandler { + /** + * Optional invoker for registered middleware in a chain. + * To be injected via SequenceActions.INVOKE_MIDDLEWARE. + */ + @inject(SequenceActions.INVOKE_MIDDLEWARE, {optional: true}) + protected invokeMiddleware: InvokeMiddleware = () => false; + constructor( - @inject(RestBindings.Http.CONTEXT) public ctx: Context, @inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute, @inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams, @inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod, @@ -30,12 +37,13 @@ export class MySequence implements SequenceHandler { async handle(context: RequestContext) { try { const {request, response} = context; + await this.invokeMiddleware(context); const route = this.findRoute(request); const args = await this.parseParams(request, route); const result = await this.invoke(route, args); this.send(response, result); - } catch (error) { - this.reject(context, error); + } catch (err) { + this.reject(context, err); } } }