NestJS 7.x 折腾记: (3) 采用nestjs-pino作为Nest logger

简介: 内置的logger不是很满足个人的需求,所以找了下社区主流的日志实现,从log4js,winston, 到选型pino .是另外两个不好么,那倒不是.萝卜青菜各有所爱吧,pino够轻量,自定义还算丰富,性能还很高!!

网络异常,图片无法展示
|


前言


内置的logger不是很满足个人的需求,


所以找了下社区主流的日志实现,


log4js,winston, 到选型pino .


是另外两个不好么,那倒不是.


萝卜青菜各有所爱吧,


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,
  };
}
目录
相关文章
|
Linux 网络安全 Docker
docker centos7 安装ssh
docker centos7 安装ssh 原文链接:http://blog.csdn.net/mergerly/article/details/54709919 一.
5015 0
|
安全 Java API
Nest.js 实战 (三):使用 Swagger 优雅地生成 API 文档
这篇文章介绍了Swagger,它是一组开源工具,围绕OpenAPI规范帮助设计、构建、记录和使用RESTAPI。文章主要讨论了Swagger的主要工具,包括SwaggerEditor、SwaggerUI、SwaggerCodegen等。然后介绍了如何在Nest框架中集成Swagger,展示了安装依赖、定义DTO和控制器等步骤,以及如何使用Swagger装饰器。文章最后总结说,集成Swagger文档可以自动生成和维护API文档,规范API标准化和一致性,但会增加开发者工作量,需要保持注释和装饰器的准确性。
647 0
Nest.js 实战 (三):使用 Swagger 优雅地生成 API 文档
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
9476 117
|
11月前
|
人工智能 机器人 Linux
把大模型变成微信私人助手,三步搞定!
随着大模型的应用越来越广泛,相信大家都对拥有一个自己的私人AI助手越来越感兴趣。然而基于大模型遵循的"规模效应"(Scaling Law)原理,传统部署方式面临三重阻碍:高昂的运维成本、复杂的技术门槛(需掌握模型部署、量化等技术概念)以及系统集成难题。
1046 0
|
缓存 Sentinel
Sentinel核心类解读:Node
Sentinel核心类解读:Node
|
前端开发 JavaScript 中间件
React状态管理库—— zustand 为啥这么简单易用🚀
React状态管理库—— zustand 为啥这么简单易用🚀
Nest.js 实战 (一):使用过滤器优雅地统一处理响应体
这篇文章介绍了在Nest.js中如何处理接口统一返回格式的方法。首先定义了响应状态码枚举和类型,然后创建了HttpException异常过滤器来捕获HttpException类的异常并设置自定义响应逻辑。最后通过全局配置和效果预览展示了如何应用这些设置。
599 0
Nest.js 实战 (一):使用过滤器优雅地统一处理响应体
|
缓存 NoSQL Java
面试官:Redis如何实现延迟任务?
延迟任务是计划任务,用于在未来特定时间执行。常见应用场景包括定时通知、异步处理、缓存管理、计划任务、订单处理、重试机制、提醒和数据采集。Redis虽无内置延迟任务功能,但可通过过期键通知、ZSet或Redisson实现。然而,这种方法精度有限,稳定性较差,适合轻量级需求。Redisson的RDelayedQueue提供更简单的延迟队列实现。
868 9
|
安全 生物认证 网络安全
信息打点-红蓝队自动化项目&资产侦察&武器库部署&企查产权&网络空间
信息打点-红蓝队自动化项目&资产侦察&武器库部署&企查产权&网络空间
532 0
|
缓存 人工智能 算法
TCP的滑动窗口和拥塞控制
TCP的滑动窗口和拥塞控制
530 0

热门文章

最新文章