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" -}