4

Nest.js 开启静态 Web 服务和打造日志系统

 2 years ago
source link: https://iiong.com/nestjs-opens-static-web-services-and-creates-a-log-system/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Nest.js 开启静态 Web 服务和打造日志系统

Jaxson Wang • 2021年04月09日 • 编程技术 • 阅读量 17 次 • 评论量 0 个

0x0 Web 服务

因为开发前端的页面,需要展示单应用程序服务器,搜索 Nest.js 可以提供服务的地方,使用起来非常简单。安装依赖包:

yarn add @nestjs/serve-static

然后在 app.module.ts 配置启用:

import { join } from 'path'
import { ServeStaticModule } from '@nestjs/serve-static'

ServeStaticModule.forRoot({
  rootPath: join(__dirname, '..', 'public'),
  exclude: ['/api*']
})
typescript

对于 ServeStaticModule 配置选项非常简单参考:API Spec

  • rootPath 指定映射物理目录,
  • exclude 排除的路径,fastify 服务不支持改选项,具体查阅文档。

0x1 守护进程

如果需要部署服务应用守护进程,安装全局依赖包:

yarn global add pm2

pm2 start PATH_TO_YOUR_PROJECT/dist/main.js --name=YOUR_APP_NAME

pm2 start YOUR_APP_NAME
pm2 restart YOUR_APP_NAME
pm2 stop YOUR_APP_NAME

0x3 日志系统

如果服务器系统出现错误情况,查找原因依然靠着日志文件,所以这样就需要打造一个完整的日志系统。在输出文件之前先需要把系统记录器完善。目前自带无法满足基本的需求,需要新建一个 LoggerService 业务来处理:

import { LoggerService as AppLoggerService, Injectable } from '@nestjs/common'

@Injectable()
export class LoggerService implements AppLoggerService {

  log(message: string): void {
  }

  error(message: string, trace: string): void {
  }

  warn(message: string): void {
  }

  debug(message: string): void {
  }

  verbose(message: string): void {
  }
}
typescript

然后在 LoggerModuel 导入:

import { Module } from '@nestjs/common'
import { LoggerService } from './logger.service'

@Module({
  providers: [LoggerService],
  exports: [LoggerService]
})
export class LoggerModule {}
typescript

因为程序实例化上下文之外,所以不参与正常依赖注入阶段,所以需要在下面进行实例:

const app = await NestFactory.create(AppModule, {
  logger: false
})
app.useLogger(app.get(MyLogger))
await app.listen(3000)
typescript

不过上述方案有个缺点就是无法打印程序初始化的任何信息。所以就需要自定义日志系统,然后利用 log4js 存储日志记录。

yarn add log4js

改造 logger.service.ts

import { Logger as AppLogger } from '@nestjs/common'
import { Logger as log4jsLogger, configure, getLogger } from 'log4js'

export class LoggerService extends AppLogger {
  log4js: log4jsLogger

  constructor(logsDir: string) {
    super()

    configure({
      appenders: {
        all: {
          filename: `${logsDir}/nestjs.services.log`,
          type: 'dateFile',
          // 配置 layout,此处使用自定义模式 pattern
          layout: { type: 'pattern', pattern: '%d [%p] %m' },
          // 日志文件按日期(天)切割
          pattern: 'yyyy-MM-dd',
          // 回滚旧的日志文件时,保证以 .log 结尾 (只有在 alwaysIncludePattern 为 false 生效)
          keepFileExt: true,
          // 输出的日志文件名是都始终包含 pattern 日期结尾
          alwaysIncludePattern: true,
          // 指定日志保留的天数
          daysToKeep: 10
        }
      },
      categories: { default: { appenders: ['all'], level: 'all' } }
    })

    this.log4js = getLogger()
  }

  log(message: any, trace: string) {
    super.log(message, trace)
    this.log4js.info(trace, message)
  }

  error(message: any, trace: string) {
    super.error(message, trace)
    this.log4js.error(trace, message)
  }

  warn(message: any, trace: string) {
    super.warn(message, trace)
    this.log4js.warn(trace, message)
  }

  debug(message: any, trace: string) {
    super.debug(message, trace)
    this.log4js.debug(trace, message)
  }

  verbose(message: any, trace: string) {
    super.verbose(message, trace)
    this.log4js.info(trace, message)
  }
}
typescript

然后在 main.ts 替代默认的日志系统:

/*
 * Copyright (c) 2021 Jaxson
 * 项目名称:Vue-Admin-Plus-Nestjs-Api
 * 文件名称:main.ts
 * 创建日期:2021年03月27日
 * 创建作者:Jaxson
 */
import { NestFactory } from '@nestjs/core'
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'
import { ConfigService } from '@nestjs/config'

import { AppModule } from '@/app.module'
import { LoggerService } from '@/logger/logger.service'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  // 获取全局配置
  const configService = app.get<ConfigService>(ConfigService)

  const logsDir = configService.get<string>('logsDir')
  const logger = new LoggerService(logsDir)
  app.useLogger(logger)

  app.setGlobalPrefix('api')
  app.enableCors() // 启用允许跨域

  const config = new DocumentBuilder()
    .setTitle('Vue Admin Plus 管理系统接口文档')
    .setDescription('这是一份关于 Vue Admin Plus 管理系统的接口文档')
    .setVersion('1.0.0')
    .addBearerAuth()
    .build()
  const document = SwaggerModule.createDocument(app, config)
  SwaggerModule.setup('docs', app, document)

  await app.listen(configService.get<number>('port'))

  logger.log(`设置应用程序端口号:${configService.get<number>('port')}`, 'bootstrap')
  logger.log(`应用程序接口地址: http://localhost:${configService.get<number>('port')}/api`, 'bootstrap')
  logger.log(`应用程序文档地址: http://localhost:${configService.get<number>('port')}/docs`, 'bootstrap')
  logger.log('🚀 服务应用已经成功启动!', 'bootstrap')
}

bootstrap()
typescript

同样在请求接口和异常过滤器替代自带日志记录:

import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common'
import { Response, Request } from 'express'

import { LoggerService } from '@/logger/logger.service'

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  constructor(private logger: LoggerService) {}
  catch(exception: HttpException, host: ArgumentsHost) {
    const context = host.switchToHttp()
    const response = context.getResponse<Response>()
    const request = context.getRequest<Request>()
    const status = exception.getStatus()
    const message = exception.getResponse()['message']

    this.logger.log(`${request.url} - ${message}`, '非正常接口请求')

    response.status(status).json({
      statusCode: status,
      message: message,
      path: request.url,
      timestamp: new Date().toISOString()
    })
  }
}
typescript
/*
 * Copyright (c) 2021 Jaxson
 * 项目名称:Vue-Admin-Plus-Nestjs-Api
 * 文件名称:transform.interceptor.ts
 * 创建日期:2021年03月27日
 * 创建作者:Jaxson
 */

import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { Request } from 'express'

import { LoggerService } from '@/logger/logger.service'

interface Response<T> {
  data: T
}

interface HasTokenUserEntity extends Express.User {
  token?: string
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
  constructor(private logger: LoggerService) {}
  intercept(context: ExecutionContext, next: CallHandler<T>): Observable<any> {
    const request = context.switchToHttp().getRequest<Request>()
    const user: HasTokenUserEntity = request.user

    this.logger.log(request.url, '正常接口请求')

    return next.handle().pipe(
      map(data => {
        const result = data
        // 判断接口是否更新 Token
        if (user.token) result['token'] = user.token
        return {
          data: result,
          statusCode: 200,
          message: '请求成功'
        }
      })
    )
  }
}
typescript

大概就这样,不过目前还是有点粗糙,日志类别分的不是很清楚,不过目前对 log4js 不是太熟悉,后期熟悉再慢慢改造。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK