大家好,今天讲一讲最近自己的nestjs+ts项目接入aliyun-sdk做日志上报的实践。
背景
由于我们整个项目架构引入了第三方日志系统SLS,所以各个服务都得改造。 查阅 官方文档 和 官方demo 结合我们当前场景只需要考虑putLogs的情况,node-sdk使用会十分简单。
接入项目
Nest框架的核心思想是什么?依赖注入和AOP。
所以思路就是将我们的接入和上报封装成一个SLSModule,通过配置依赖的方式注入到appModule,然后在切面中使用SLSModule。
创建module
// ali-sls/service.ts
import { Injectable, Logger } from '@nestjs/common'
import { GenerateSLSDto, projectName, registerConfig, shouldPutLogRoutes, storeName } from './helper'
import { SlsVo } from './sls.dto'
import { SLS } from 'aliyun-sdk'
@Injectable()
export class SlsService {
private slsServer: any
constructor() {
this.slsServer = new SLS(registerConfig)
}
putLogs(slsVo: SlsVo): void {
// 过滤不需要上报日志的接口路径
if (!Object.keys(shouldPutLogRoutes).includes(slsVo.requestUri)) return
this.slsServer.putLogs(
{
projectName: projectName,
logStoreName: storeName,
logGroup: {
logs: [GenerateSLSDto(slsVo)]
}
},
function (err) {
Logger.log('SLS上报成功!')
if (err) {
Logger.error('SLS上报失败:' + err)
return
}
}
)
}
}
我们通过在创建service实例的构造函数中实例化SLS对象,然后在appModule中imports这个模块。
切面上报
整理出来我的上报需求:
- 针对部分接口上报
- 执行成功上报成功提示
- 执行失败上报异常内容
具体到代码实现,是在拦截器和异常过滤器中分别上报成功和失败的日志。
// resInterceptor.ts
// 拦截器原先职责是统计接口耗时以及封装响应参数格式
// 新添加一段上报任务
@Injectable()
export class ResInterceptor<T> implements NestInterceptor<T, Response<T>> {
// 注入了我们的SlsService服务
constructor(private slsService: SlsService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
const now = Date.now()
const ctx = context.switchToHttp()
const request = ctx.getRequest()
Logger.warn(`调用 ${request.url}开始`)
return next.handle().pipe(
map(data => {
Logger.debug(`调用 ${request.url} 结束: ${Date.now() - now}ms`)
// 上报
this.slsService.putLogs({
logData: '',
requestUri: request.url,
jobStatus: 'success',
logLevel: 'info'
})
if (request.url.startsWith('/fsEvent')) {
return data
} else {
return { data: data, code: 200, msg: 'success' }
}
})
)
}
}
// AllExceptionsFilter.ts
// 全局捕获处理异常类,抛出的任意异常都在这里处理
// 在这里统一封装了异常的返回内容和格式
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common'
import { BaseExceptionFilter } from '@nestjs/core'
import { SlsService } from '@/ali-sls/sls.service'
@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter implements ExceptionFilter {
// 注入我们的SlsService服务
constructor(private slsService: SlsService) {
super()
}
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp()
const response = ctx.getResponse()
const request = ctx.getRequest()
const isHttpErr = exception instanceof HttpException
isHttpErr ? Logger.error(JSON.stringify(exception, null, 2)) : Logger.error(exception)
const status = isHttpErr ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR
response.status(status).json({
code: status,
timestamp: new Date().toLocaleDateString(),
path: request.url,
message: exception.response || '内部异常'
})
// 上报
this.slsService.putLogs({
logData: exception.response ? JSON.stringify(exception.response) : '内部异常',
requestUri: request.url,
jobStatus: 'fail',
logLevel: 'error'
})
}
}
由于异常类是全局注册的,需要手动将实例传入。
// main.ts
const slsService = app.get<SlsService>(SlsService)
app.useGlobalFilters(new AllExceptionsFilter(slsService))
思路就是全局拦截并调用putLogs方法,在方法内部去过滤有效的路由。在切面中写的优势在于业务代码侵入性小,也意味着工作量小,后续扩展简单方便。
解决ts报错
由于没有找到sdk的.d.ts文件,ts编译会报错。
// /types/aliyun-sdk.d.ts
declare module 'aliyun-sdk' {
export class SLS {
constructor(config: Record<string, string>) {}
}
}
"typeRoots": ["node_modules/@types", "types"]
可以如上通过手动定义模块来解决这个报错。
总结
实现的效果其实和官方demo用express是一样的,只是不同的框架思想不同,写法不同。
Nest这套思想其实和JAVA的Spring那套基本是一样的~~
最后,感谢阅读!