From f8418f440aa2a769c1b52019db132a10d905e1dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 8 Feb 2018 15:51:26 +0100 Subject: [PATCH] Add a deprecation message pointing to our monorepo --- .gitignore | 3 - .npmrc | 1 - .prettierignore | 2 - .travis.yml | 9 - LICENSE | 21 - README.md | 363 +----------------- appveyor.yml | 16 - index.d.ts | 6 - index.js | 8 - index.ts | 6 - package.json | 45 --- src/component.ts | 16 - src/decorators/log.decorator.ts | 44 --- src/index.ts | 13 - src/keys.ts | 25 -- src/mixins/log-level.mixin.ts | 45 --- src/providers/log-action.provider.ts | 92 ----- src/providers/log-level.provider.ts | 15 - src/providers/timer.provider.ts | 18 - src/types.ts | 26 -- test/acceptance/log.extension.acceptance.ts | 327 ---------------- test/mocha.opts | 2 - test/smoke.test.ts | 12 - test/unit/decorators/log.decorator.unit.ts | 29 -- test/unit/mixins/log-level.mixin.unit.ts | 36 -- .../providers/log-action.provider.unit.ts | 83 ---- .../unit/providers/log-level.provider.unit.ts | 14 - test/unit/providers/timer.provider.unit.ts | 26 -- tsconfig.json | 3 - 29 files changed, 2 insertions(+), 1304 deletions(-) delete mode 100644 .gitignore delete mode 100644 .npmrc delete mode 100644 .prettierignore delete mode 100644 .travis.yml delete mode 100644 LICENSE delete mode 100644 appveyor.yml delete mode 100644 index.d.ts delete mode 100644 index.js delete mode 100644 index.ts delete mode 100644 package.json delete mode 100644 src/component.ts delete mode 100644 src/decorators/log.decorator.ts delete mode 100644 src/index.ts delete mode 100644 src/keys.ts delete mode 100644 src/mixins/log-level.mixin.ts delete mode 100644 src/providers/log-action.provider.ts delete mode 100644 src/providers/log-level.provider.ts delete mode 100644 src/providers/timer.provider.ts delete mode 100644 src/types.ts delete mode 100644 test/acceptance/log.extension.acceptance.ts delete mode 100644 test/mocha.opts delete mode 100644 test/smoke.test.ts delete mode 100644 test/unit/decorators/log.decorator.unit.ts delete mode 100644 test/unit/mixins/log-level.mixin.unit.ts delete mode 100644 test/unit/providers/log-action.provider.unit.ts delete mode 100644 test/unit/providers/log-level.provider.unit.ts delete mode 100644 test/unit/providers/timer.provider.unit.ts delete mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 02cd92d..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -dist -dist6 -node_modules diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 43c97e7..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -package-lock=false diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 94427a0..0000000 --- a/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -dist -dist6 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ddf2af4..0000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -sudo: false -language: node_js -node_js: - - "6" - - "8" - -branches: - only: - - master diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 4c4f935..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 StrongLoop and IBM API Connect - -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/README.md b/README.md index 55b8d49..4c6372c 100644 --- a/README.md +++ b/README.md @@ -1,364 +1,5 @@ # loopback4-example-log-extension -An example repo showing how to write a complex log extension for LoopBack 4 -## Overview +This example has been moved to our monorepo, see -This repository shows you how to use [@loopback/cli](https://github.com/strongloop/loopback-next/tree/master/packages/cli) -to write a complex logging extension that requires a [Component](http://loopback.io/doc/en/lb4/Using-components.html), -[Decorator](http://loopback.io/doc/en/lb4/Decorators.html), and a [Mixin](http://loopback.io/doc/en/lb4/Mixin.html). - -To use the extension, load the component to get access to a `LogFn` that can be -used in a sequence to log information. A Mixin allows you to set the -application wide logLevel. Only Controller methods configured at or above the -logLevel will be logged. - -Possible levels are: DEBUG < INFO < WARN < ERROR < OFF - -*Possible levels are represented as numbers but users can use `LOG_LEVEL.${level}` -to specify the value instead of using numbers.* - -A decorator enables you to provide metadata for Controller methods to set the -minimum logLevel. - -### Example Usage - -```ts -import { - LogLevelMixin, - LogComponent, - LOG_LEVEL, - log -} from 'loopback4-example-log-extension'; -// Other imports ... - -class LogApp extends LogLevelMixin(Application) { - constructor() { - super({ - components: [RestComponent, LogComponent], - logLevel: LOG_LEVEL.ERROR, - controllers: [MyController] - }); - }; -} - -class MyController { - @log(LOG_LEVEL.WARN) - @get('/') - hello() { - return 'Hello LoopBack'; - } - - @log(LOG_LEVEL.ERROR) - @get('/name') - helloName() { - return 'Hello Name' - } -} -``` - -## Tutorial - -Install `@loopback/cli` by running `npm i -g @loopback/cli`. - -Initialize your new extension project as follows: -`lb4 extension` - -- Project name: `loopback4-example-log-extension` -- Project description: `An example extension project for LoopBack 4` -- Project root directory: `(loopback4-example-log-extension)` -- Component class name: `LogComponent` -- Select project build settings: `Enable tslint, Enable prettier, Enable mocha, Enable loopbackBuild` - -Now you can write the extension as follows: - -### `/src/keys.ts` -Define `Binding` keys here for the component as well as any constants for the -user (for this extension that'll be the logLevel `enum`). - -```ts -export namespace EXAMPLE_LOG_BINDINGS { - export const METADATA = 'example.log.metadata'; - export const APP_LOG_LEVEL = 'example.log.level'; - export const TIMER = 'example.log.timer'; - export const LOG_ACTION = 'example.log.action'; -} - -export enum LOG_LEVEL { - DEBUG, - INFO, - WARN, - ERROR, - OFF, -} -``` - -### `src/types.ts` -Define TypeScript type definitions / interfaces for complex types and functions here. - -```ts -import {ParsedRequest, OperationArgs} from '@loopback/rest'; - -export interface LogFn { - ( - req: ParsedRequest, - args: OperationArgs, - result: any, - startTime?: HighResTime, - ): Promise; - - startTimer(): HighResTime; -} - -export type LevelMetadata = {level: number}; -export type HighResTime = [number, number]; // [seconds, nanoseconds] -export type TimerFn = (start?: HighResTime) => HighResTime; -``` - -### `src/decorators/log.decorator.ts` -Extension users can use decorators to provide "hints" (or metadata) for our -component. These "hints" allow the extension to modify behaviour accordingly. - -For this extension, the decorator marks which controller methods should be -logged (and optionally at which level they should be logged). -`Reflector` from `@loopback/context` is used to store and retrieve the metadata -by the extension. - -```ts -import {LOG_LEVEL, EXAMPLE_LOG_BINDINGS} from '../keys'; -import {Constructor, Reflector} from '@loopback/context'; -import {LevelMetadata} from '../types'; - -export function log(level?: number) { - return function(target: Object, methodName: string): void { - if (level === undefined) level = LOG_LEVEL.WARN; - Reflector.defineMetadata( - EXAMPLE_LOG_BINDINGS.METADATA, - {level}, - target, - methodName, - ); - }; -} - -export function getLogMetadata( - controllerClass: Constructor<{}>, - methodName: string, -): LevelMetadata { - return Reflector.getMetadata( - EXAMPLE_LOG_BINDINGS.METADATA, - controllerClass.prototype, - methodName, - ); -} -``` - -### `src/mixins/log-level.mixin.ts` -Extension users must set an app wide log level at or above which the decorated -controller methods will be logged. A user can do so by binding the level to -`example.log.level` but this can be a hassle. - -A mixin makes it easier for the user to set the application wide log level by -providing it via `ApplicationOptions` or using a helper method `app.logLevel(level: number)`. - -```ts -import {Constructor} from '@loopback/context'; -import {EXAMPLE_LOG_BINDINGS} from '../keys'; - -export function LogLevelMixin>(superClass: T) { - return class extends superClass { - constructor(...args: any[]) { - super(...args); - if (!this.options) this.options = {}; - - if (this.options.logLevel) { - this.logLevel(this.options.logLevel); - } - } - - logLevel(level: number) { - this.bind(EXAMPLE_LOG_BINDINGS.APP_LOG_LEVEL).to(level); - } - }; -} -``` - -### Providers -A Providers is a class that returns a `value()` function that can be invoked by -LoopBack 4. - -### `src/providers/timer.provider.ts` -A timer than can be used to time the function that is being logged. - -```ts -import {Provider} from '@loopback/context'; -import {TimerFn, HighResTime} from '../types'; - -export class TimerProvider implements Provider { - constructor() {} - value(): TimerFn { - return (start?: HighResTime): HighResTime => { - if (!start) return process.hrtime(); - return process.hrtime(start); - }; - } -} -``` - -### `src/providers/log-level.provider.ts` -A provider can set the default binding value for `example.log.level` so it's -easier to get started with the extension. User's can override the value by -binding a new value or using the mixin. - -```ts -import {Provider} from '@loopback/context'; -import {LOG_LEVEL} from '../keys'; - -export class LogLevelProvider implements Provider { - constructor() {} - value(): number { - return LOG_LEVEL.WARN; - } -} -``` - -### `src/providers/log-action.provider.ts` -This will be the most important provider for the extension as it is responsible -for actually logging the request. The extension will retrieve the metadata -stored by the `@log()` decorator using the controller and method name. -Since bindings are resolved at runtime and these values change with each request, -`inject.getter()` must be used to get a function capable of resolving the value -when called. The action provider will look as follows: - -```ts -import {inject, Provider, Constructor, Getter} from '@loopback/context'; -import {CoreBindings} from '@loopback/core'; -import {OperationArgs, ParsedRequest} from '@loopback/rest'; -import {getLogMetadata} from '../decorators/log.decorator'; -import {EXAMPLE_LOG_BINDINGS, LOG_LEVEL} from '../keys'; -import {LogFn, TimerFn, HighResTime, LevelMetadata} from '../types'; -import chalk from 'chalk'; - -export class LogActionProvider implements Provider { - constructor( - @inject.getter(CoreBindings.CONTROLLER_CLASS) - private readonly getController: Getter>, - @inject.getter(CoreBindings.CONTROLLER_METHOD_NAME) - private readonly getMethod: Getter, - @inject(EXAMPLE_LOG_BINDINGS.APP_LOG_LEVEL) - private readonly logLevel: number, - @inject(EXAMPLE_LOG_BINDINGS.TIMER) public timer: TimerFn, - ) {} - - value(): LogFn { - const fn = (( - req: ParsedRequest, - args: OperationArgs, - result: any, - start?: HighResTime, - ) => { - return this.action(req, args, result, start); - }); - - fn.startTimer = () => { - return this.timer(); - }; - - return fn; - } - - private async action( - req: ParsedRequest, - args: OperationArgs, - result: any, - start?: HighResTime, - ): Promise { - const controllerClass = await this.getController(); - const methodName: string = await this.getMethod(); - const metadata: LevelMetadata = getLogMetadata(controllerClass, methodName); - const level: number | undefined = metadata ? metadata.level : undefined; - - if ( - level !== undefined && - this.logLevel !== LOG_LEVEL.OFF && - level >= this.logLevel && - level !== LOG_LEVEL.OFF - ) { - if (!args) args = []; - let log = `${req.url} :: ${controllerClass.name}.`; - log += `${methodName}(${args.join(', ')}) => `; - - if (typeof result === 'object') log += JSON.stringify(result); - else log += result; - - if (start) { - const timeDiff: HighResTime = this.timer(start); - const time: number = - timeDiff[0] * 1000 + Math.round(timeDiff[1] * 1e-4) / 100; - log = `${time}ms: ${log}`; - } - - switch (level) { - case LOG_LEVEL.DEBUG: - console.log(chalk.white(`DEBUG: ${log}`)); - break; - case LOG_LEVEL.INFO: - console.log(chalk.green(`INFO: ${log}`)); - break; - case LOG_LEVEL.WARN: - console.log(chalk.yellow(`WARN: ${log}`)); - break; - case LOG_LEVEL.ERROR: - console.log(chalk.red(`ERROR: ${log}`)); - break; - } - } - } -} -``` - -### `src/index.ts` -Export all the files to ensure a user can import the necessary components. - -```ts -export * from './decorators/log.decorator'; -export * from './mixins/log-level.mixin'; -export * from './providers/log-action.provider'; -export * from './providers/log-level.provider'; -export * from './providers/timer.provider'; -export * from './component'; -export * from './types'; -export * from './keys'; -``` - -### `src/component.ts` -Package the providers in the component to their appropriate `Binding` keys so -they are automatically bound when a user adds the component to their application. - -```ts -import {EXAMPLE_LOG_BINDINGS} from './keys'; -import {Component, ProviderMap} from '@loopback/core'; -import {TimerProvider, LogActionProvider, LogLevelProvider} from './'; - -export class LogComponent implements Component { - providers?: ProviderMap = { - [EXAMPLE_LOG_BINDINGS.TIMER]: TimerProvider, - [EXAMPLE_LOG_BINDINGS.LOG_ACTION]: LogActionProvider, - [EXAMPLE_LOG_BINDINGS.APP_LOG_LEVEL]: LogLevelProvider, - }; -} -``` - -## Testing - -Tests should be written to ensure the behaviour implemented is correct and -future modifications don't break this expected behavior *(unless it's -intentional in which case the tests should be updated as well)*. - -Take a look at the test folder to see the variety of tests written for this -extension. There are unit tests to test functionality of individual functions -as well as an extension acceptance test which tests the entire extension as a -whole (everything working together). - -## License - -MIT License +https://github.com/strongloop/loopback-next/tree/master/packages/example-log-extension diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index df61f9d..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,16 +0,0 @@ -environment: - matrix: - - nodejs_version: "6" - - nodejs_version: "8" - -install: - - ps: Install-Product node $env:nodejs_version - - npm install - -test_script: - - node --version - - npm --version - - npm test - -build: off -skip_branch_with_pr: true \ No newline at end of file diff --git a/index.d.ts b/index.d.ts deleted file mode 100644 index 526e763..0000000 --- a/index.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -export * from './dist'; diff --git a/index.js b/index.js deleted file mode 100644 index a96e67f..0000000 --- a/index.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -const nodeMajorVersion = +process.versions.node.split('.')[0]; -const dist = nodeMajorVersion >= 7 ? './dist' : './dist6'; -module.exports = require(dist); diff --git a/index.ts b/index.ts deleted file mode 100644 index 6de3de5..0000000 --- a/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -export * from './src'; diff --git a/package.json b/package.json deleted file mode 100644 index 876102b..0000000 --- a/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "loopback4-example-log-extension", - "version": "1.0.0", - "description": "An example extension project for LoopBack 4", - "main": "index.js", - "engines": { - "node": ">=8" - }, - "scripts": { - "build": "lb-tsc", - "build:watch": "lb-tsc --watch", - "clean": "lb-clean", - "lint": "npm run prettier:check && npm run tslint", - "lint:fix": "npm run prettier:fix && npm run tslint", - "prettier:cli": "lb-prettier \"**/*.ts\" \"**/*.js\"", - "prettier:check": "npm run prettier:cli -- -l", - "prettier:fix": "npm run prettier:cli -- --write", - "tslint": "lb-tslint", - "tslint:fix": "npm run tslint -- --fix", - "prepare": "npm run build", - "pretest": "npm run clean && npm run build", - "test": "lb-dist mocha DIST/test", - "posttest": "npm run lint" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/strongloop/loopback4-example-log-extension.git" - }, - "keywords": ["loopback", "loopback-extension"], - "license": "MIT", - "bugs": { - "url": "https://github.com/strongloop/loopback4-example-log-extension/issues" - }, - "homepage": "https://github.com/strongloop/loopback4-example-log-extension#readme", - "devDependencies": { - "@loopback/build": "^4.0.0-alpha.3", - "@loopback/testlab": "^4.0.0-alpha.12" - }, - "dependencies": { - "@loopback/context": "^4.0.0-alpha.17", - "@loopback/core": "^4.0.0-alpha.19", - "@loopback/rest": "^4.0.0-alpha.6", - "chalk": "^2.3.0" - } -} diff --git a/src/component.ts b/src/component.ts deleted file mode 100644 index de6d000..0000000 --- a/src/component.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {EXAMPLE_LOG_BINDINGS} from './keys'; -import {Component, ProviderMap} from '@loopback/core'; -import {TimerProvider, LogActionProvider, LogLevelProvider} from './'; - -export class LogComponent implements Component { - providers?: ProviderMap = { - [EXAMPLE_LOG_BINDINGS.TIMER]: TimerProvider, - [EXAMPLE_LOG_BINDINGS.LOG_ACTION]: LogActionProvider, - [EXAMPLE_LOG_BINDINGS.APP_LOG_LEVEL]: LogLevelProvider, - }; -} diff --git a/src/decorators/log.decorator.ts b/src/decorators/log.decorator.ts deleted file mode 100644 index aa1c9f2..0000000 --- a/src/decorators/log.decorator.ts +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {LOG_LEVEL, EXAMPLE_LOG_BINDINGS} from '../keys'; -import {Constructor, Reflector} from '@loopback/context'; -import {LevelMetadata} from '../types'; - -/** - * Mark a controller method as requiring logging (input, output & timing) - * if it is set at or greater than Application LogLevel. - * LOG_LEVEL.DEBUG < LOG_LEVEL.INFO < LOG_LEVEL.WARN < LOG_LEVEL.ERROR < LOG_LEVEL.OFF - * - * @param level The Log Level at or above it should log - */ -export function log(level?: number) { - return function(target: Object, methodName: string): void { - if (level === undefined) level = LOG_LEVEL.WARN; - Reflector.defineMetadata( - EXAMPLE_LOG_BINDINGS.METADATA, - {level}, - target, - methodName, - ); - }; -} - -/** - * Fetch log level stored by `@log` decorator. - * - * @param controllerClass Target controller - * @param methodName Target method - */ -export function getLogMetadata( - controllerClass: Constructor<{}>, - methodName: string, -): LevelMetadata { - return Reflector.getMetadata( - EXAMPLE_LOG_BINDINGS.METADATA, - controllerClass.prototype, - methodName, - ); -} diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index f03f113..0000000 --- a/src/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -export * from './decorators/log.decorator'; -export * from './mixins/log-level.mixin'; -export * from './providers/log-action.provider'; -export * from './providers/log-level.provider'; -export * from './providers/timer.provider'; -export * from './component'; -export * from './types'; -export * from './keys'; diff --git a/src/keys.ts b/src/keys.ts deleted file mode 100644 index 896afde..0000000 --- a/src/keys.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -/** - * Binding keys used by this component. - */ -export namespace EXAMPLE_LOG_BINDINGS { - export const METADATA = 'example.log.metadata'; - export const APP_LOG_LEVEL = 'example.log.level'; - export const TIMER = 'example.log.timer'; - export const LOG_ACTION = 'example.log.action'; -} - -/** - * Enum to define the supported log levels - */ -export enum LOG_LEVEL { - DEBUG, - INFO, - WARN, - ERROR, - OFF, -} diff --git a/src/mixins/log-level.mixin.ts b/src/mixins/log-level.mixin.ts deleted file mode 100644 index f0e1801..0000000 --- a/src/mixins/log-level.mixin.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {Constructor} from '@loopback/context'; -import {EXAMPLE_LOG_BINDINGS} from '../keys'; - -/** - * A mixin class for Application that can bind logLevel from `options`. - * Also provides .logLevel() to bind application wide logLevel. - * Functions with a log level set to logLevel or higher sill log data - * - * ```ts - * class MyApplication extends LogLevelMixin(Application) {} - * ``` - */ -// tslint:disable-next-line:no-any -export function LogLevelMixin>(superClass: T) { - return class extends superClass { - // A mixin class has to take in a type any[] argument! - // tslint:disable-next-line:no-any - constructor(...args: any[]) { - super(...args); - if (!this.options) this.options = {}; - - if (this.options.logLevel) { - this.logLevel(this.options.logLevel); - } - } - - /** - * Set minimum logLevel to be displayed. - * - * @param level The log level to set for @log decorator - * - * ```ts - * app.logLevel(LogLevel.INFO); - * ``` - */ - logLevel(level: number) { - this.bind(EXAMPLE_LOG_BINDINGS.APP_LOG_LEVEL).to(level); - } - }; -} diff --git a/src/providers/log-action.provider.ts b/src/providers/log-action.provider.ts deleted file mode 100644 index a212bd8..0000000 --- a/src/providers/log-action.provider.ts +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {inject, Provider, Constructor, Getter} from '@loopback/context'; -import {CoreBindings} from '@loopback/core'; -import {OperationArgs, ParsedRequest} from '@loopback/rest'; -import {getLogMetadata} from '../decorators/log.decorator'; -import {EXAMPLE_LOG_BINDINGS, LOG_LEVEL} from '../keys'; -import {LogFn, TimerFn, HighResTime, LevelMetadata} from '../types'; -import chalk from 'chalk'; - -export class LogActionProvider implements Provider { - constructor( - @inject.getter(CoreBindings.CONTROLLER_CLASS) - private readonly getController: Getter>, - @inject.getter(CoreBindings.CONTROLLER_METHOD_NAME) - private readonly getMethod: Getter, - @inject(EXAMPLE_LOG_BINDINGS.APP_LOG_LEVEL) - private readonly logLevel: number, - @inject(EXAMPLE_LOG_BINDINGS.TIMER) public timer: TimerFn, - ) {} - - value(): LogFn { - const fn = (( - req: ParsedRequest, - args: OperationArgs, - // tslint:disable-next-line:no-any - result: any, - start?: HighResTime, - ) => { - return this.action(req, args, result, start); - }); - - fn.startTimer = () => { - return this.timer(); - }; - - return fn; - } - - private async action( - req: ParsedRequest, - args: OperationArgs, - // tslint:disable-next-line:no-any - result: any, - start?: HighResTime, - ): Promise { - const controllerClass = await this.getController(); - const methodName: string = await this.getMethod(); - - const metadata: LevelMetadata = getLogMetadata(controllerClass, methodName); - const level: number | undefined = metadata ? metadata.level : undefined; - - if ( - level !== undefined && - this.logLevel !== LOG_LEVEL.OFF && - level >= this.logLevel && - level !== LOG_LEVEL.OFF - ) { - if (!args) args = []; - let log = `${req.url} :: ${controllerClass.name}.`; - log += `${methodName}(${args.join(', ')}) => `; - - if (typeof result === 'object') log += JSON.stringify(result); - else log += result; - - if (start) { - const timeDiff: HighResTime = this.timer(start); - const time: number = - timeDiff[0] * 1000 + Math.round(timeDiff[1] * 1e-4) / 100; - log = `${time}ms: ${log}`; - } - - switch (level) { - case LOG_LEVEL.DEBUG: - console.log(chalk.white(`DEBUG: ${log}`)); - break; - case LOG_LEVEL.INFO: - console.log(chalk.green(`INFO: ${log}`)); - break; - case LOG_LEVEL.WARN: - console.log(chalk.yellow(`WARN: ${log}`)); - break; - case LOG_LEVEL.ERROR: - console.log(chalk.red(`ERROR: ${log}`)); - break; - } - } - } -} diff --git a/src/providers/log-level.provider.ts b/src/providers/log-level.provider.ts deleted file mode 100644 index fdbe1d5..0000000 --- a/src/providers/log-level.provider.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {Provider} from '@loopback/context'; -import {LOG_LEVEL} from '../keys'; - -export class LogLevelProvider implements Provider { - constructor() {} - - value(): number { - return LOG_LEVEL.WARN; - } -} diff --git a/src/providers/timer.provider.ts b/src/providers/timer.provider.ts deleted file mode 100644 index 02be863..0000000 --- a/src/providers/timer.provider.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {Provider} from '@loopback/context'; -import {TimerFn, HighResTime} from '../types'; - -export class TimerProvider implements Provider { - constructor() {} - - value(): TimerFn { - return (start?: HighResTime): HighResTime => { - if (!start) return process.hrtime(); - return process.hrtime(start); - }; - } -} diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 7f1a5a7..0000000 --- a/src/types.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -// Types and interfaces exposed by the extension go here - -import {ParsedRequest, OperationArgs} from '@loopback/rest'; - -export interface LogFn { - ( - req: ParsedRequest, - args: OperationArgs, - // tslint:disable-next-line:no-any - result: any, - startTime?: HighResTime, - ): Promise; - - startTimer(): HighResTime; -} - -export type LevelMetadata = {level: number}; - -export type HighResTime = [number, number]; // [seconds, nanoseconds] - -export type TimerFn = (start?: HighResTime) => HighResTime; diff --git a/test/acceptance/log.extension.acceptance.ts b/test/acceptance/log.extension.acceptance.ts deleted file mode 100644 index 4501cde..0000000 --- a/test/acceptance/log.extension.acceptance.ts +++ /dev/null @@ -1,327 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {Application} from '@loopback/core'; -import { - RestComponent, - RestServer, - get, - param, - SequenceHandler, - RestBindings, - FindRoute, - ParseParams, - InvokeMethod, - Send, - Reject, - ParsedRequest, - ServerResponse, -} from '@loopback/rest'; -import { - LogComponent, - LogLevelMixin, - LOG_LEVEL, - log, - EXAMPLE_LOG_BINDINGS, - LogFn, - HighResTime, -} from '../..'; -import { - sinon, - SinonSpy, - Client, - createClientForHandler, - expect, -} from '@loopback/testlab'; -import {Context, inject} from '@loopback/context'; -import chalk from 'chalk'; - -const SequenceActions = RestBindings.SequenceActions; - -describe('log extension acceptance test', () => { - let app: LogApp; - let server: RestServer; - let spy: SinonSpy; - - class LogApp extends LogLevelMixin(Application) {} - - const debugMatch: string = chalk.white( - 'DEBUG: /debug :: MyController.debug() => debug called', - ); - const infoMatch: string = chalk.green( - 'INFO: /info :: MyController.info() => info called', - ); - const warnMatch: string = chalk.yellow( - 'WARN: /warn :: MyController.warn() => warn called', - ); - const errorMatch: string = chalk.red( - 'ERROR: /error :: MyController.error() => error called', - ); - const nameMatch: string = chalk.yellow( - 'WARN: /?name=test :: MyController.hello(test) => hello test', - ); - - beforeEach(createApp); - beforeEach(createController); - beforeEach(createSequence); - beforeEach(createConsoleSpy); - - afterEach(restoreConsoleSpy); - - it('logs information at DEBUG or higher', async () => { - setAppLogToDebug(); - const client: Client = createClientForHandler(server.handleHttp); - - await client.get('/nolog').expect(200, 'nolog called'); - expect(spy.called).to.be.False(); - - await client.get('/off').expect(200, 'off called'); - expect(spy.called).to.be.False(); - - await client.get('/debug').expect(200, 'debug called'); - sinon.assert.calledWith(spy, debugMatch); - - await client.get('/info').expect(200, 'info called'); - sinon.assert.calledWith(spy, infoMatch); - - await client.get('/warn').expect(200, 'warn called'); - sinon.assert.calledWith(spy, warnMatch); - - await client.get('/error').expect(200, 'error called'); - sinon.assert.calledWith(spy, errorMatch); - - await client.get('/?name=test').expect(200, 'hello test'); - sinon.assert.calledWith(spy, nameMatch); - }); - - it('logs information at INFO or higher', async () => { - setAppLogToInfo(); - const client: Client = createClientForHandler(server.handleHttp); - - await client.get('/nolog').expect(200, 'nolog called'); - expect(spy.called).to.be.False(); - - await client.get('/off').expect(200, 'off called'); - expect(spy.called).to.be.False(); - - await client.get('/debug').expect(200, 'debug called'); - expect(spy.called).to.be.False(); - - await client.get('/info').expect(200, 'info called'); - sinon.assert.calledWith(spy, infoMatch); - - await client.get('/warn').expect(200, 'warn called'); - sinon.assert.calledWith(spy, warnMatch); - - await client.get('/error').expect(200, 'error called'); - sinon.assert.calledWith(spy, errorMatch); - - await client.get('/?name=test').expect(200, 'hello test'); - sinon.assert.calledWith(spy, nameMatch); - }); - - it('logs information at WARN or higher', async () => { - setAppLogToWarn(); - const client: Client = createClientForHandler(server.handleHttp); - - await client.get('/nolog').expect(200, 'nolog called'); - expect(spy.called).to.be.False(); - - await client.get('/off').expect(200, 'off called'); - expect(spy.called).to.be.False(); - - await client.get('/debug').expect(200, 'debug called'); - expect(spy.called).to.be.False(); - - await client.get('/info').expect(200, 'info called'); - expect(spy.called).to.be.False(); - - await client.get('/warn').expect(200, 'warn called'); - sinon.assert.calledWith(spy, warnMatch); - - await client.get('/error').expect(200, 'error called'); - sinon.assert.calledWith(spy, errorMatch); - - await client.get('/?name=test').expect(200, 'hello test'); - sinon.assert.calledWith(spy, nameMatch); - }); - - it('logs information at ERROR', async () => { - setAppLogToError(); - const client: Client = createClientForHandler(server.handleHttp); - - await client.get('/nolog').expect(200, 'nolog called'); - expect(spy.called).to.be.False(); - - await client.get('/off').expect(200, 'off called'); - expect(spy.called).to.be.False(); - - await client.get('/debug').expect(200, 'debug called'); - expect(spy.called).to.be.False(); - - await client.get('/info').expect(200, 'info called'); - expect(spy.called).to.be.False(); - - await client.get('/warn').expect(200, 'warn called'); - expect(spy.called).to.be.False(); - - await client.get('/?name=test').expect(200, 'hello test'); - expect(spy.called).to.be.False(); - - await client.get('/error').expect(200, 'error called'); - sinon.assert.calledWith(spy, errorMatch); - }); - - it('logs no information when logLevel is set to OFF', async () => { - setAppLogToOff(); - const client: Client = createClientForHandler(server.handleHttp); - - await client.get('/nolog').expect(200, 'nolog called'); - expect(spy.called).to.be.False(); - - await client.get('/off').expect(200, 'off called'); - expect(spy.called).to.be.False(); - - await client.get('/debug').expect(200, 'debug called'); - expect(spy.called).to.be.False(); - - await client.get('/info').expect(200, 'info called'); - expect(spy.called).to.be.False(); - - await client.get('/warn').expect(200, 'warn called'); - expect(spy.called).to.be.False(); - - await client.get('/?name=test').expect(200, 'hello test'); - expect(spy.called).to.be.False(); - - await client.get('/error').expect(200, 'error called'); - expect(spy.called).to.be.False(); - }); - - function createSequence() { - class LogSequence implements SequenceHandler { - 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, - @inject(SequenceActions.SEND) protected send: Send, - @inject(SequenceActions.REJECT) protected reject: Reject, - @inject(EXAMPLE_LOG_BINDINGS.LOG_ACTION) protected logger: LogFn, - ) {} - - async handle(req: ParsedRequest, res: ServerResponse) { - // tslint:disable-next-line:no-any - let args: any = []; - // tslint:disable-next-line:no-any - let result: any; - - try { - const route = this.findRoute(req); - args = await this.parseParams(req, route); - result = await this.invoke(route, args); - this.send(res, result); - } catch (err) { - this.reject(res, req, err); - result = err; - } - - await this.logger(req, args, result); - } - } - - server.sequence(LogSequence); - } - - async function createApp() { - app = new LogApp({ - components: [RestComponent, LogComponent], - }); - - app.bind(EXAMPLE_LOG_BINDINGS.TIMER).to(timer); - server = await app.getServer(RestServer); - } - - function setAppLogToDebug() { - app.logLevel(LOG_LEVEL.DEBUG); - } - - function setAppLogToWarn() { - app.logLevel(LOG_LEVEL.WARN); - } - - function setAppLogToError() { - app.logLevel(LOG_LEVEL.ERROR); - } - - function setAppLogToInfo() { - app.logLevel(LOG_LEVEL.INFO); - } - - function setAppLogToOff() { - app.logLevel(LOG_LEVEL.OFF); - } - - function createController() { - class MyController { - @get('/debug') - @log(LOG_LEVEL.DEBUG) - debug() { - return 'debug called'; - } - - @get('/warn') - @log(LOG_LEVEL.WARN) - warn() { - return 'warn called'; - } - - @get('/info') - @log(LOG_LEVEL.INFO) - info() { - return 'info called'; - } - - @get('/error') - @log(LOG_LEVEL.ERROR) - error() { - return 'error called'; - } - - @get('/off') - @log(LOG_LEVEL.OFF) - off() { - return 'off called'; - } - - @get('/') - @log() - hello(@param.query.string('name') name: string) { - return `hello ${name}`; - } - - @get('/nolog') - nolog() { - return 'nolog called'; - } - } - - app.controller(MyController); - } - - function timer(startTime?: HighResTime): HighResTime { - if (!startTime) return [3, 3]; - return [2, 2]; - } - - function createConsoleSpy() { - spy = sinon.spy(console, 'log'); - } - - function restoreConsoleSpy() { - spy.restore(); - } -}); diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index d5bd344..0000000 --- a/test/mocha.opts +++ /dev/null @@ -1,2 +0,0 @@ ---recursive ---reporter dot diff --git a/test/smoke.test.ts b/test/smoke.test.ts deleted file mode 100644 index 8f89fa4..0000000 --- a/test/smoke.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {expect} from '@loopback/testlab'; - -describe('Smoke test to verify project setup - remove me later', () => { - it('works', () => { - expect(true).to.equal(true); - }); -}); diff --git a/test/unit/decorators/log.decorator.unit.ts b/test/unit/decorators/log.decorator.unit.ts deleted file mode 100644 index 58d7518..0000000 --- a/test/unit/decorators/log.decorator.unit.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {expect} from '@loopback/testlab'; -import {log, getLogMetadata, LOG_LEVEL, LevelMetadata} from '../../..'; - -describe('@log() decorator (unit)', () => { - it('sets log level for method to given value', () => { - class TestClass { - @log(LOG_LEVEL.ERROR) - test() {} - } - - const level: LevelMetadata = getLogMetadata(TestClass, 'test'); - expect(level.level).to.be.eql(LOG_LEVEL.ERROR); - }); - - it('sets log level for method to default', () => { - class TestClass { - @log() - test() {} - } - - const level: LevelMetadata = getLogMetadata(TestClass, 'test'); - expect(level.level).to.be.eql(LOG_LEVEL.WARN); - }); -}); diff --git a/test/unit/mixins/log-level.mixin.unit.ts b/test/unit/mixins/log-level.mixin.unit.ts deleted file mode 100644 index 83f93ca..0000000 --- a/test/unit/mixins/log-level.mixin.unit.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {expect} from '@loopback/testlab'; -import {Application} from '@loopback/core'; -import {LogLevelMixin, LOG_LEVEL, EXAMPLE_LOG_BINDINGS} from '../../..'; - -describe('LogLevelMixin (unit)', () => { - it('mixed class has .logLevel()', () => { - const myApp = new AppWithLogLevel(); - expect(typeof myApp.logLevel).to.be.eql('function'); - }); - - it('binds LogLevel from constructor', () => { - const myApp = new AppWithLogLevel({ - logLevel: LOG_LEVEL.ERROR, - }); - - expectLogLevelToBeBound(myApp); - }); - - it('bind logLevel from app.logLevel()', () => { - const myApp = new AppWithLogLevel(); - myApp.logLevel(LOG_LEVEL.ERROR); - expectLogLevelToBeBound(myApp); - }); - - class AppWithLogLevel extends LogLevelMixin(Application) {} - - function expectLogLevelToBeBound(myApp: Application) { - const logLevel = myApp.getSync(EXAMPLE_LOG_BINDINGS.APP_LOG_LEVEL); - expect(logLevel).to.be.eql(LOG_LEVEL.ERROR); - } -}); diff --git a/test/unit/providers/log-action.provider.unit.ts b/test/unit/providers/log-action.provider.unit.ts deleted file mode 100644 index b69bf4c..0000000 --- a/test/unit/providers/log-action.provider.unit.ts +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-log-extension -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {sinon} from '@loopback/testlab'; -import {ParsedRequest} from '@loopback/rest'; -import {Context} from '@loopback/context'; -import { - LogActionProvider, - LogFn, - log, - EXAMPLE_LOG_BINDINGS, - LOG_LEVEL, - HighResTime, -} from '../../..'; -import {CoreBindings} from '@loopback/core'; -import chalk from 'chalk'; - -describe('LogActionProvider (unit)', () => { - let spy: sinon.SinonSpy; - let logger: LogFn; - const req = {url: '/test'}; - - beforeEach(createConsoleSpy); - beforeEach(getLogger); - - afterEach(restoreConsoleSpy); - - it('logs a value without a start time', async () => { - const match = chalk.red('ERROR: /test :: TestClass.test() => test message'); - - await logger(req, [], 'test message'); - sinon.assert.calledWith(spy, match); - }); - - it('logs a value with a start time', async () => { - const match = chalk.red( - 'ERROR: 100ms: /test :: TestClass.test() => test message', - ); - const startTime: HighResTime = logger.startTimer(); - - await logger(req, [], 'test message', startTime); - sinon.assert.calledWith(spy, match); - }); - - it('logs a value with args present', async () => { - const match = chalk.red( - 'ERROR: /test :: TestClass.test(test, message) => test message', - ); - - await logger(req, ['test', 'message'], 'test message'); - sinon.assert.calledWith(spy, match); - }); - - async function getLogger() { - class TestClass { - @log(LOG_LEVEL.ERROR) - test() {} - } - - const context: Context = new Context(); - context.bind(CoreBindings.CONTROLLER_CLASS).to(TestClass); - context.bind(CoreBindings.CONTROLLER_METHOD_NAME).to('test'); - context.bind(EXAMPLE_LOG_BINDINGS.APP_LOG_LEVEL).to(LOG_LEVEL.WARN); - context.bind(EXAMPLE_LOG_BINDINGS.TIMER).to(timer); - context.bind(EXAMPLE_LOG_BINDINGS.LOG_ACTION).toProvider(LogActionProvider); - logger = await context.get(EXAMPLE_LOG_BINDINGS.LOG_ACTION); - } - - function createConsoleSpy() { - spy = sinon.spy(console, 'log'); - } - - function restoreConsoleSpy() { - spy.restore(); - } - - function timer(startTime?: HighResTime): HighResTime { - if (!startTime) return [3, 3]; - else return [0, 100000002]; - } -}); diff --git a/test/unit/providers/log-level.provider.unit.ts b/test/unit/providers/log-level.provider.unit.ts deleted file mode 100644 index bcf332a..0000000 --- a/test/unit/providers/log-level.provider.unit.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-extension-starter -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {expect} from '@loopback/testlab'; -import {LogLevelProvider, LOG_LEVEL} from '../../..'; - -describe('LogLevelProvider (unit)', () => { - it('returns LOG_LEVEL.WARN as default level', () => { - const level = new LogLevelProvider().value(); - expect(level).to.be.eql(LOG_LEVEL.WARN); - }); -}); diff --git a/test/unit/providers/timer.provider.unit.ts b/test/unit/providers/timer.provider.unit.ts deleted file mode 100644 index 0163880..0000000 --- a/test/unit/providers/timer.provider.unit.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-extension-starter -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -import {expect} from '@loopback/testlab'; -import {TimerProvider} from '../../..'; -import {TimerFn, HighResTime} from '../../..'; - -describe('TimerProvider (unit)', () => { - it('returns current time given no start time', () => { - const timer: TimerFn = new TimerProvider().value(); - const time: HighResTime = timer(); - expect(time).to.have.lengthOf(2); - expect(time[0]).to.be.a.Number(); - expect(time[1]).to.be.a.Number(); - }); - - it('returns the time difference given a time', () => { - const timer: TimerFn = new TimerProvider().value(); - const diff: HighResTime = timer([2, 2]); - expect(diff).to.have.lengthOf(2); - expect(diff[0]).to.be.a.Number(); - expect(diff[1]).to.be.a.Number(); - }); -}); diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 0cc7f89..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "./node_modules/@loopback/build/config/tsconfig.common.json" -}