nestjs-pino
Advanced tools
Comparing version
@@ -1,2 +0,4 @@ | ||
import { LoggerService, DynamicModule, NestModule, MiddlewareConsumer } from "@nestjs/common"; | ||
import { LoggerService, DynamicModule } from "@nestjs/common"; | ||
import { ModuleMetadata } from "@nestjs/common/interfaces"; | ||
import pinoHttp from "pino-http"; | ||
import pino, { LoggerOptions, DestinationStream } from "pino"; | ||
@@ -6,26 +8,22 @@ declare type PassedLogger = { | ||
}; | ||
declare type Params = [] | [PassedLogger] | [LoggerOptions | DestinationStream] | [LoggerOptions, DestinationStream]; | ||
export declare type Params = null | pinoHttp.Options | DestinationStream | [pinoHttp.Options, DestinationStream]; | ||
export interface LoggerModuleAsyncOptions extends Pick<ModuleMetadata, "imports" | "providers"> { | ||
useFactory: (...args: any[]) => Params; | ||
inject?: any[]; | ||
} | ||
export declare class Logger implements LoggerService { | ||
verbose(msg: string, ...args: any[]): void; | ||
verbose(obj: object, msg?: string, ...args: any[]): void; | ||
debug(msg: string, ...args: any[]): void; | ||
debug(obj: object, msg?: string, ...args: any[]): void; | ||
log(msg: string, ...args: any[]): void; | ||
log(obj: object, msg?: string, ...args: any[]): void; | ||
warn(msg: string, ...args: any[]): void; | ||
warn(obj: object, msg?: string, ...args: any[]): void; | ||
error(msg: string, ...args: any[]): void; | ||
error(obj: object, msg?: string, ...args: any[]): void; | ||
private readonly outOfContext; | ||
constructor(options: Params); | ||
verbose(message: any, context?: string, ...args: any[]): void; | ||
debug(message: any, context?: string, ...args: any[]): void; | ||
log(message: any, context?: string, ...args: any[]): void; | ||
warn(message: any, context?: string, ...args: any[]): void; | ||
error(message: any, trace?: string, context?: string, ...args: any[]): void; | ||
private readonly logger; | ||
} | ||
export declare class LoggerModule implements NestModule { | ||
static forRoot(...params: Params): DynamicModule; | ||
configure(consumer: MiddlewareConsumer): void; | ||
export declare class LoggerModule { | ||
static forRoot(opts?: PassedLogger | LoggerOptions | DestinationStream): DynamicModule; | ||
static forRoot(opts: LoggerOptions, stream: DestinationStream): DynamicModule; | ||
static forRootAsync(options: LoggerModuleAsyncOptions): DynamicModule; | ||
} | ||
declare global { | ||
namespace Express { | ||
interface Request { | ||
log: pino.Logger; | ||
} | ||
} | ||
} | ||
export {}; |
@@ -8,77 +8,160 @@ "use strict"; | ||
}; | ||
var __metadata = (this && this.__metadata) || function (k, v) { | ||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); | ||
}; | ||
var __param = (this && this.__param) || function (paramIndex, decorator) { | ||
return function (target, key) { decorator(target, key, paramIndex); } | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
var LoggerModule_1; | ||
var LoggerModule_1, LoggerCoreModule_1; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
"use strict"; | ||
const common_1 = require("@nestjs/common"); | ||
const express_pino_logger_1 = __importDefault(require("express-pino-logger")); | ||
const pino_http_1 = __importDefault(require("pino-http")); | ||
const pino_1 = __importDefault(require("pino")); | ||
const express_ctx_1 = require("express-ctx"); | ||
let outOfContextLogger; | ||
const loggerKey = "logger"; | ||
let rootParams; | ||
const LOGGER_KEY = "logger"; | ||
const OPTIONS_PROVIDER_TOKEN = "pino-options"; | ||
let Logger = class Logger { | ||
verbose(...params) { | ||
return (express_ctx_1.getValue(loggerKey) || outOfContextLogger).trace(...params); | ||
constructor(options) { | ||
if (Array.isArray(options)) { | ||
this.outOfContext = pino_1.default(...options); | ||
} | ||
else if (isPassedLogger(options)) { | ||
this.outOfContext = options.logger; | ||
} | ||
else { | ||
this.outOfContext = pino_1.default(options || undefined); | ||
} | ||
} | ||
debug(...params) { | ||
return (express_ctx_1.getValue(loggerKey) || outOfContextLogger).debug(...params); | ||
verbose(message, context, ...args) { | ||
if (context) { | ||
this.logger.trace({ context }, message, ...args); | ||
} | ||
else { | ||
this.logger.trace(message); | ||
} | ||
} | ||
log(...params) { | ||
return (express_ctx_1.getValue(loggerKey) || outOfContextLogger).info(...params); | ||
debug(message, context, ...args) { | ||
if (context) { | ||
this.logger.debug({ context }, message, ...args); | ||
} | ||
else { | ||
this.logger.debug(message); | ||
} | ||
} | ||
warn(...params) { | ||
return (express_ctx_1.getValue(loggerKey) || outOfContextLogger).warn(...params); | ||
log(message, context, ...args) { | ||
if (context) { | ||
this.logger.info({ context }, message, ...args); | ||
} | ||
else { | ||
this.logger.info(message); | ||
} | ||
} | ||
error(...params) { | ||
return (express_ctx_1.getValue(loggerKey) || outOfContextLogger).error(...params); | ||
warn(message, context, ...args) { | ||
if (context) { | ||
this.logger.warn({ context }, message, ...args); | ||
} | ||
else { | ||
this.logger.warn(message); | ||
} | ||
} | ||
error(message, trace, context, ...args) { | ||
if (context) { | ||
this.logger.error({ context, trace }, message, ...args); | ||
} | ||
else if (trace) { | ||
this.logger.error({ trace }, message); | ||
} | ||
else { | ||
this.logger.error(message); | ||
} | ||
} | ||
get logger() { | ||
return express_ctx_1.getValue(LOGGER_KEY) || this.outOfContext; | ||
} | ||
}; | ||
Logger = __decorate([ | ||
common_1.Injectable() | ||
common_1.Injectable(), | ||
__param(0, common_1.Inject(OPTIONS_PROVIDER_TOKEN)), | ||
__metadata("design:paramtypes", [Object]) | ||
], Logger); | ||
exports.Logger = Logger; | ||
let LoggerModule = LoggerModule_1 = class LoggerModule { | ||
static forRoot(...params) { | ||
rootParams = params; | ||
if (hasLoggerParamsPassedLogger(rootParams)) { | ||
outOfContextLogger = rootParams[0].logger; | ||
} | ||
else { | ||
outOfContextLogger = pino_1.default(...rootParams); | ||
} | ||
static forRoot(opts, stream) { | ||
return { | ||
module: LoggerModule_1, | ||
providers: [Logger], | ||
imports: [ | ||
LoggerCoreModule.forRoot(stream ? [opts, stream] : opts) | ||
] | ||
}; | ||
} | ||
static forRootAsync(options) { | ||
return { | ||
module: LoggerModule_1, | ||
imports: [LoggerCoreModule.forRootAsync(options)] | ||
}; | ||
} | ||
}; | ||
LoggerModule = LoggerModule_1 = __decorate([ | ||
common_1.Module({}) | ||
], LoggerModule); | ||
exports.LoggerModule = LoggerModule; | ||
let LoggerCoreModule = LoggerCoreModule_1 = class LoggerCoreModule { | ||
constructor(options) { | ||
this.options = options; | ||
} | ||
static forRoot(options) { | ||
const optionsProvider = { | ||
provide: OPTIONS_PROVIDER_TOKEN, | ||
useValue: options || null | ||
}; | ||
return { | ||
module: LoggerCoreModule_1, | ||
providers: [Logger, optionsProvider], | ||
exports: [Logger] | ||
}; | ||
} | ||
static forRootAsync(options) { | ||
const optionsProvider = { | ||
provide: OPTIONS_PROVIDER_TOKEN, | ||
useFactory: options.useFactory, | ||
inject: options.inject | ||
}; | ||
return { | ||
module: LoggerCoreModule_1, | ||
imports: options.imports, | ||
providers: options.providers | ||
? [Logger, optionsProvider, ...options.providers] | ||
: [Logger, optionsProvider], | ||
exports: [Logger] | ||
}; | ||
} | ||
configure(consumer) { | ||
consumer | ||
.apply(...createLoggerMiddlewares()) | ||
.apply(...createLoggerMiddlewares(this.options)) | ||
.forRoutes({ path: "*", method: common_1.RequestMethod.ALL }); | ||
} | ||
}; | ||
LoggerModule = LoggerModule_1 = __decorate([ | ||
LoggerCoreModule = LoggerCoreModule_1 = __decorate([ | ||
common_1.Global(), | ||
common_1.Module({ | ||
providers: [Logger], | ||
exports: [Logger] | ||
}) | ||
], LoggerModule); | ||
exports.LoggerModule = LoggerModule; | ||
function createLoggerMiddlewares() { | ||
return [ | ||
express_ctx_1.middleware, | ||
express_pino_logger_1.default(...rootParams), | ||
(req, res, next) => { | ||
express_ctx_1.setValue(loggerKey, req.log); | ||
next(); | ||
} | ||
]; | ||
common_1.Module({ providers: [Logger], exports: [Logger] }), | ||
__param(0, common_1.Inject(OPTIONS_PROVIDER_TOKEN)), | ||
__metadata("design:paramtypes", [Object]) | ||
], LoggerCoreModule); | ||
function isPassedLogger(params) { | ||
return !!params && "logger" in params; | ||
} | ||
function hasLoggerParamsPassedLogger(params) { | ||
return params[0] && "logger" in params[0]; | ||
function createLoggerMiddlewares(params) { | ||
const middleware = Array.isArray(params) | ||
? pino_http_1.default(...params) | ||
: pino_http_1.default(params || undefined); | ||
return [express_ctx_1.middleware, middleware, bindLoggerMiddleware]; | ||
} | ||
function bindLoggerMiddleware(req, res, next) { | ||
express_ctx_1.setValue(LOGGER_KEY, req.log); | ||
next(); | ||
} | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "nestjs-pino", | ||
"version": "0.2.1", | ||
"version": "0.3.0", | ||
"description": "Pino logger for NestJS", | ||
"main": "dist/index.js", | ||
"scripts": { | ||
"test": "jest", | ||
"build": "tsc --p tsconfig.build.json", | ||
"test": "jest --verbose", | ||
"build": "rm -rf ./dist && tsc --p tsconfig.build.json", | ||
"report": "cat ./coverage/lcov.info | coveralls", | ||
"example": "tsc && LOG_LEVEL=debug node dist/example/main" | ||
"example": "rm -rf ./dist && tsc && LOG_LEVEL=debug node dist/example/main" | ||
}, | ||
@@ -35,3 +35,3 @@ "engineStrict": false, | ||
"express-ctx": "^0.1.1", | ||
"express-pino-logger": "^4.0.0" | ||
"pino-http": "^4.3.0" | ||
}, | ||
@@ -45,6 +45,6 @@ "devDependencies": { | ||
"@types/express": "^4.17.1", | ||
"@types/express-pino-logger": "^4.0.1", | ||
"@types/jest": "^24.0.18", | ||
"@types/memorystream": "^0.3.0", | ||
"@types/node": "^12.7.4", | ||
"@types/pino-http": "^4.3.2", | ||
"@types/supertest": "^2.0.8", | ||
@@ -67,3 +67,2 @@ "coveralls": "^3.0.6", | ||
], | ||
"rootDir": "src", | ||
"testRegex": ".spec.ts$", | ||
@@ -75,4 +74,7 @@ "transform": { | ||
"coverageDirectory": "../coverage", | ||
"collectCoverageFrom": [ | ||
"src/**/*.ts" | ||
], | ||
"testEnvironment": "node" | ||
} | ||
} |
126
README.md
@@ -27,2 +27,15 @@ <p align="center"> | ||
Import module: | ||
```ts | ||
import { LoggerModule } from 'nestjs-pino'; | ||
@Module({ | ||
imports: [LoggerModule.forRoot()], | ||
controllers: [AppController], | ||
providers: [MyService] | ||
}) | ||
class MyModule {} | ||
``` | ||
In controller: | ||
@@ -42,3 +55,3 @@ | ||
getHello(): string { | ||
this.logger.log("calling AppController.getHello"); | ||
this.logger.log("getHello()", AppController.name); | ||
return `Hello ${this.myService.getWorld()}`; | ||
@@ -58,4 +71,4 @@ } | ||
getWorld() { | ||
this.logger.debug("calling MyService.getWorld"); | ||
getWorld(...params: any[]) { | ||
this.logger.log("getWorld(%o)", MyService.name, params); | ||
return "World!"; | ||
@@ -66,8 +79,17 @@ } | ||
Output (every log has request context): | ||
Output: | ||
```json | ||
{"level":30,"time":1568720266616,"pid":25566,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","headers":{...},"remoteAddress":"::1","remotePort":53753},"msg":"calling AppController.getHello","v":1} | ||
{"level":20,"time":1568720266616,"pid":25566,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","headers":{...},"remoteAddress":"::1","remotePort":53753},"msg":"calling MyService.getWorld","v":1} | ||
{"level":30,"time":1568720266623,"pid":25566,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","headers":{...},"remoteAddress":"::1","remotePort":53753},"res":{"statusCode":200,"headers":{...}},"responseTime":9,"msg":"request completed","v":1} | ||
// Logs by Nest itself, when set `app.useLogger(app.get(Logger))` | ||
{"level":30,"time":1570470154387,"pid":17383,"hostname":"my-host","context":"RoutesResolver","msg":"AppController {/}: true","v":1} | ||
{"level":30,"time":1570470154391,"pid":17383,"hostname":"my-host","context":"RouterExplorer","msg":"Mapped {/, GET} route true","v":1} | ||
{"level":30,"time":1570470154405,"pid":17383,"hostname":"my-host","context":"NestApplication","msg":"Nest application successfully started true","v":1} | ||
// Logs by injected Logger methods in Services/Controllers | ||
// Every log has it's request data | ||
{"level":30,"time":1570470161805,"pid":17383,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","headers":{...},"remoteAddress":"::1","remotePort":53957},"context":"AppController","msg":"getHello()","v":1} | ||
{"level":30,"time":1570470161805,"pid":17383,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","headers":{...},"remoteAddress":"::1","remotePort":53957},"context":"MyService","msg":"getWorld([])","v":1} | ||
// Automatic logs of every request/response | ||
{"level":30,"time":1570470161819,"pid":17383,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","headers":{...},"remoteAddress":"::1","remotePort":53957},"res":{"statusCode":304,"headers":{...}},"responseTime":15,"msg":"request completed","v":1} | ||
``` | ||
@@ -97,5 +119,5 @@ | ||
### Configure | ||
### Synchronous configuration | ||
Also, you can configure it. `forRoot` function has the same API as [express-pino-logger](https://github.com/pinojs/express-pino-logger#api) has (it's the same as [pino itself](https://github.com/pinojs/pino/blob/master/docs/api.md#options) and can take existing logger via `{ logger: pino(...) }`): | ||
`LoggerModule.forRoot` has the same API as [pino-http](https://github.com/pinojs/pino-http#pinohttpopts-stream): | ||
@@ -123,2 +145,64 @@ ```ts | ||
### Asynchronous configuration | ||
With `LoggerModule.forRootAsync` you can for example import your `ConfigModule` and inject `ConfigService` to use it in `useFactory` method. | ||
`useFactory` should return result typeof arguments of [pino-http](https://github.com/pinojs/pino-http#pinohttpopts-stream) or `null`, example: | ||
```ts | ||
import { LoggerModule } from 'nestjs-pino'; | ||
@Injectable() | ||
class ConfigService { | ||
public readonly level = "debug"; | ||
} | ||
@Module({ | ||
providers: [ConfigService], | ||
exports: [ConfigService] | ||
}) | ||
class ConfigModule {} | ||
@Module({ | ||
imports: [ | ||
LoggerModule.forRootAsync({ | ||
imports: [ConfigModule], | ||
inject: [ConfigService], | ||
useFactory: (config: ConfigService) => { | ||
return { level: config.level }; | ||
} | ||
}) | ||
], | ||
... | ||
}) | ||
class TestModule {} | ||
``` | ||
Or without `ConfigModule` you can just pass `ConfigService` to `providers`: | ||
```ts | ||
import { LoggerModule } from 'nestjs-pino'; | ||
@Injectable() | ||
class ConfigService { | ||
public readonly level = "debug"; | ||
public readonly stream = stream; | ||
} | ||
@Module({ | ||
imports: [ | ||
LoggerModule.forRootAsync({ | ||
providers: [ConfigService], | ||
inject: [ConfigService], | ||
useFactory: (config: ConfigService) => { | ||
return [{ level: config.level }, config.stream]; | ||
} | ||
}) | ||
], | ||
controllers: [TestController] | ||
}) | ||
class TestModule {} | ||
``` | ||
### Extreme mode | ||
@@ -158,4 +242,4 @@ | ||
getWorld() { | ||
this.logger.debug("calling MyService.getWorld"); | ||
getWorld(...params: any[]) { | ||
this.logger.log("getWorld(%o)", MyService.name, params); | ||
return "World!"; | ||
@@ -168,6 +252,9 @@ } | ||
According to [official docs](https://docs.nestjs.com/techniques/logger#dependency-injection), loggers with Dependency injection should be set via following construction: | ||
```ts | ||
import { Logger } from 'nestjs-pino'; | ||
const app = await NestFactory.create(MyModule, { logger: new Logger() }); | ||
const app = await NestFactory.create(MyModule, { logger: false }); | ||
app.useLogger(app.get(Logger)); | ||
``` | ||
@@ -179,3 +266,3 @@ | ||
__A__: It use [express-pino-logger](https://github.com/pinojs/express-pino-logger) under hood, so every request has it's own [child-logger](https://github.com/pinojs/pino/blob/master/docs/child-loggers.md), and with help of [async_hooks](https://nodejs.org/api/async_hooks.html) `Logger` can get it while calling own methods. So your logs can be groupped by `req.id`. | ||
__A__: It use [pino-http](https://github.com/pinojs/pino-http) under hood, so every request has it's own [child-logger](https://github.com/pinojs/pino/blob/master/docs/child-loggers.md), and with help of [async_hooks](https://nodejs.org/api/async_hooks.html) `Logger` can get it while calling own methods. So your logs can be groupped by `req.id`. | ||
@@ -193,6 +280,9 @@ __Q__: _Why use [async_hooks](https://nodejs.org/api/async_hooks.html) instead of [REQUEST scope](https://docs.nestjs.com/fundamentals/injection-scopes#per-request-injection)?_ | ||
__A__: Pino built in methods are not compatible to NestJS built in `LoggerService` methods, so decision is to map pino methods to `LoggerService` methods to save `Logger` API: | ||
- `trace`=`verbose` | ||
- `debug`=`debug` | ||
- `info`=`log` | ||
- `warn`=`warn` | ||
- `error`=`error` | ||
| pino | LoggerService | | ||
| ------- | ------------- | | ||
| `trace` | `verbose` | | ||
| `debug` | `debug` | | ||
| `info` | `log` | | ||
| `warn` | `warn` | | ||
| `error` | `error` | |
{ | ||
"extends": "./tsconfig.json", | ||
"exclude": ["node_modules", "dist", "**/*.spec.ts", "example"] | ||
"include": ["src"] | ||
} |
@@ -13,5 +13,6 @@ { | ||
"incremental": true, | ||
"esModuleInterop": true | ||
"esModuleInterop": true, | ||
"strict": true | ||
}, | ||
"exclude": ["node_modules", "dist"] | ||
"include": ["src", "example", "__tests__"] | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
346143
7.72%16
60%716
442.42%279
47.62%1
Infinity%+ Added
- Removed
- Removed