网络异常,图片无法展示
|
前言
内置的logger不是很满足个人的需求,
所以找了下社区主流的日志实现,
是另外两个不好么,那倒不是.
萝卜青菜各有所爱吧,
pino够轻量,自定义还算丰富,性能还很高!!
效果图
开发模式
INFO [2020-11-09 08:45:12.336 +0000] (56588 on crper-MBP.local): AppController {/api/v1}: context: "RoutesResolver" INFO [2020-11-09 08:45:12.340 +0000] (56588 on crper-MBP.local): Mapped {/api/v1, GET} route context: "RouterExplorer" INFO [2020-11-09 08:45:12.341 +0000] (56588 on crper-MBP.local): Mapped {/api/v1/post, POST} route context: "RouterExplorer" INFO [2020-11-09 08:45:12.341 +0000] (56588 on crper-MBP.local): Mapped {/api/v1/user, GET} route context: "RouterExplorer" INFO [2020-11-09 08:45:12.342 +0000] (56588 on crper-MBP.local): Mapped {/api/v1/netease-news/:id, GET} route context: "RouterExplorer" INFO [2020-11-09 08:45:12.344 +0000] (56588 on crper-MBP.local): Nest application successfully started context: "NestApplication" Swagger文档链接: http://localhost:3000/api-docs Restful接口链接: http://localhost:3000/api/v1 AppController newDz Before... AppController newDz After... +1761ms INFO [2020-11-09 08:45:52.082 +0000] (56588 on crper-MBP.local): request completed 响应信息: { "statusCode": 200, "headers": { "content-security-policy": "default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests", "x-dns-prefetch-control": "off", "expect-ct": "max-age=0", "x-frame-options": "SAMEORIGIN", "strict-transport-security": "max-age=15552000; includeSubDomains", "x-download-options": "noopen", "x-content-type-options": "nosniff", "x-permitted-cross-domain-policies": "none", "referrer-policy": "no-referrer", "x-xss-protection": "0", "content-type": "application/json; charset=utf-8", "content-length": "853", "etag": "W/\"355-KpR/5mF8Y34126QG9UV2LArJxBw\"", "vary": "Accept-Encoding" } } 响应时间(ms): 1772 请求信息: { "id": 1, "method": "GET", "url": "/api/v1/netease-news/11", "headers": { "host": "localhost:3000", "user-agent": "insomnia/2020.4.2", "content-type": "application/json", "accept": "*/*", "content-length": "23" }, "remoteAddress": "::ffff:127.0.0.1", "remotePort": 61069, "httpVersion": "1.1", "params": { "0": "/netease-news/11" }, "query": {}, "body": { "asdfs": "12321" } }
生产模式
2020-11-09 16:46 +08:00: /Users/linqunhe/Code/aozhe/h3yun-frontend-bff/h3yun-bff-core/config/env/dev.local.env 2020-11-09 16:46 +08:00: /Users/linqunhe/Code/aozhe/h3yun-frontend-bff/h3yun-bff-core/config/env/http.env 2020-11-09 16:46 +08:00: /Users/linqunhe/Code/aozhe/h3yun-frontend-bff/h3yun-bff-core/config/env/report.env 2020-11-09 16:46 +08:00: {"level":30,"time":1604911609493,"pid":57279,"hostname":"crper-MBP.local","context":"RoutesResolver","msg":"AppController {/api/v1}:"} 2020-11-09 16:46 +08:00: {"level":30,"time":1604911609494,"pid":57279,"hostname":"crper-MBP.local","context":"RouterExplorer","msg":"Mapped {/api/v1, GET} route"} 2020-11-09 16:46 +08:00: {"level":30,"time":1604911609495,"pid":57279,"hostname":"crper-MBP.local","context":"RouterExplorer","msg":"Mapped {/api/v1/post, POST} route"} 2020-11-09 16:46 +08:00: {"level":30,"time":1604911609495,"pid":57279,"hostname":"crper-MBP.local","context":"RouterExplorer","msg":"Mapped {/api/v1/user, GET} route"} 2020-11-09 16:46 +08:00: {"level":30,"time":1604911609495,"pid":57279,"hostname":"crper-MBP.local","context":"RouterExplorer","msg":"Mapped {/api/v1/netease-news/:id, GET} route"} 2020-11-09 16:46 +08:00: {"level":30,"time":1604911609498,"pid":57279,"hostname":"crper-MBP.local","context":"NestApplication","msg":"Nest application successfully started"} 2020-11-09 16:46 +08:00: Swagger文档链接: http://localhost:3000/api-docs 2020-11-09 16:46 +08:00: Restful接口链接: http://localhost:3000/api/v1 2020-11-09 16:46 +08:00: AppController newDz Before... 2020-11-09 16:46 +08:00: AppController newDz After... +1552ms 2020-11-09 16:46 +08:00: {"level":30,"time":1604911614547,"pid":57279,"hostname":"crper-MBP.local","请求信息":{"id":1,"method":"GET","url":"/api/v1/netease-news/11","headers":{"host":"localhost:3000","user-agent":"insomnia/2020.4.2","content-type":"application/json","accept":"*/*","content-length":"23"},"remoteAddress":"::ffff:127.0.0.1","remotePort":61103,"httpVersion":"1.1","params":{"0":"/netease-news/11"},"query":{},"body":{"asdfs":"12321"}},"响应信息":{"statusCode":200,"headers":{"content-security-policy":"default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests","x-dns-prefetch-control":"off","expect-ct":"max-age=0","x-frame-options":"SAMEORIGIN","strict-transport-security":"max-age=15552000; includeSubDomains","x-download-options":"noopen","x-content-type-options":"nosniff","x-permitted-cross-domain-policies":"none","referrer-policy":"no-referrer","x-xss-protection":"0","content-type":"application/json; charset=utf-8","content-length":"853","etag":"W/\"355-KpR/5mF8Y34126QG9UV2LArJxBw\"","vary":"Accept-Encoding"}},"响应时间(ms)":1561,"msg":"request completed"}
实战
安装
# nestjs-pino 是基于pino封装的nest模块,可以拿来即用! # https://github.com/iamolegga/nestjs-pino yarn add nestjs-pino # pino 日志美化工具(用于开发模式美滋滋,看效果图的开发模式) yarn add -D pino-pretty
配置
主入口(main.ts)
关闭内置的nest logger功能
import { AppModule } from './app.module'; import { NestFactory } from '@nestjs/core'; async function bootstrap() { const app = await NestFactory.create(AppModule, { cors: false, // 关闭cors logger: false, // 关闭内置logger }); await app.listen(configService.get('SERVE_LISTENER_PORT')); } bootstrap()
根模块配置(app.module)
正如我们上篇文章说的,都搞了配置中心了,
那肯定这边同步引用相关配置啊,不然意义何在!
还是同样的姿势,从typescript声明入手
import { DynamicModule } from "@nestjs/common"; import { LoggerModuleAsyncParams, Params } from "./params"; export declare class LoggerModule { static forRoot(params?: Params | undefined): DynamicModule; // 同步注册的方式,看下面参数声明 static forRootAsync(params: LoggerModuleAsyncParams): DynamicModule; // 异步加载配置!我们用这个 } export interface Params { pinoHttp?: pinoHttp.Options | DestinationStream | [pinoHttp.Options, DestinationStream]; // pino-http的参数配置 exclude?: Parameters<MiddlewareConfigProxy["exclude"]>; // 就是可以设置排除不作用的路由区域,具体可以看下官方文档的中间件部分! forRoutes?: Parameters<MiddlewareConfigProxy["forRoutes"]>; // 同理,上面,路由作用域 useExisting?: true; // 这个东西就是检测已存在的pino就不用这个了,比如用了Fastify作为底层,它内置logger就是走的pino!!! renameContext?: string; // 重命名上下文,一般不碰它,默认context也很好理解 } export interface LoggerModuleAsyncParams extends Pick<ModuleMetadata, "imports" | "providers"> { useFactory: (...args: any[]) => Params | Promise<Params>; // 工厂函数,返回上面的params定义的规格就能识别 inject?: any[]; // 是否要注入一些provider提供的功能,我们会用到(配置中心), // 用inject必然会依赖module,也就是import,定义里面也pick了ModuleMetadata的定义! }
import * as Joi from '@hapi/joi'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { HttpModule, Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerModule } from 'nestjs-pino'; import envReportConfig from './config/env/report.config'; import envSwaggerConfig from './config/env/swagger.config'; import { getDirAllFileNameArr } from './utils/get-dir-all-file-name-arr'; import { pinoHttpOption } from './config/module/pino-http-option.config'; @Module({ imports: [ LoggerModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: async (configService: ConfigService) => { return { pinoHttp: pinoHttpOption(configService.get('NODE_ENV')) }; }, }), ConfigModule.forRoot({ encoding: 'utf-8', envFilePath: [...getDirAllFileNameArr()], expandVariables: true, // 开启嵌套变量 ignoreEnvVars: true, load: [envReportConfig, envSwaggerConfig], validationSchema: Joi.object({ H3_APM_SERVER_URL: Joi.string().default(''), H3_LATEINOS_REPORT_URL: Joi.string().default(''), SERVE_LISTENER_PORT: Joi.number().default(3000), SWAGGER_SETUP_PATH: Joi.string().default('api-docs'), SWAGGER_ENDPOINT_PREFIX: Joi.string().default('api/v1'), SWAGGER_UI_TITLE: Joi.string().default('Swagger文档标题'), SWAGGER_UI_TITLE_DESC: Joi.string().default('赶紧改相关配置啊~~'), SWAGGER_API_VERSION: Joi.string().default('1.0'), HTTP_TIMEOUT: Joi.number().default(5000), HTTP_MAX_REDIRECTS: Joi.number().default(5), NODE_ENV: Joi.string() .valid('development', 'production', 'test', 'provision') .default('development'), }), validationOptions: { allowUnknown: false, // 控制是否允许环境变量中未知的键。默认为true。 abortEarly: true, // 如果为true,在遇到第一个错误时就停止验证;如果为false,返回所有错误。默认为false。 }, }), ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
pinoHttpOption
我把pino的配置抽离出去了,
不然密密麻麻一坨,维护性很差!!
// pino-http 配置 // https://github.com/pinojs/pino-http import PinoHttp from 'pino-http'; export function pinoHttpOption(envDevMode = 'development'): PinoHttp.Options { return { customAttributeKeys: { req: '请求信息', res: '响应信息', err: '错误信息', responseTime: '响应时间(ms)', }, level: envDevMode !== 'production' ? 'debug' : 'info', customLogLevel(res: { statusCode: number }, err: any) { if (res.statusCode >= 400 && res.statusCode < 500) { return 'warn'; } else if (res.statusCode >= 500 || err) { return 'error'; } return 'info'; }, serializers: { req(req: { httpVersion: any; raw: { httpVersion: any; params: any; query: any; body: any }; params: any; query: any; body: any; }) { req.httpVersion = req.raw.httpVersion; req.params = req.raw.params; req.query = req.raw.query; req.body = req.raw.body; return req; }, err(err: { params: any; raw: { params: any; query: any; body: any }; query: any; body: any; }) { err.params = err.raw.params; err.query = err.raw.query; err.body = err.raw.body; return err; }, }, prettyPrint: envDevMode === 'development' ? { colorize: true, levelFirst: true, translateTime: 'yyyy-mm-dd HH:MM:ss.l o', } : false, }; }