NestJS日志系统与Winston集成
NestJS日志系统与Winston集成
学习目标:掌握 NestJS 内置日志系统,学会使用 Winston 进行日志管理和文件存储。
一、日志系统的作用
1.1 为什么需要日志
日志的作用:
日志系统的作用:
│
├── 问题定位
│ ├── 线上问题排查
│ ├── Bug 复现分析
│ └── 性能瓶颈定位
│
├── 运行监控
│ ├── 系统运行状态
│ ├── 接口调用情况
│ └── 错误发生频率
│
├── 审计追溯
│ ├── 用户操作记录
│ ├── 数据变更记录
│ └── 安全事件追溯
│
└── 性能分析
├── 接口响应时间
├── 数据库查询时间
└── 系统资源消耗1.2 日志级别
NestJS 内置日志级别:
| 级别 | 颜色 | 用途 | 使用场景 |
|---|---|---|---|
error | 红色 | 严重错误 | 系统异常、数据库连接失败 |
warn | 黄色 | 警告信息 | 配置缺失、参数异常 |
log | 绿色 | 一般信息 | 接口调用、业务流程 |
verbose | 白色 | 详细信息 | 调试信息、详细流程 |
debug | 蓝色 | 调试信息 | 开发调试、变量值 |
Winston 日志级别(从高到低):
Winston 日志级别(优先级从高到低):
│
├── error - 错误:严重问题
├── warn - 警告:潜在问题
├── info - 信息:重要事件(NestJS 的 log)
├── http - HTTP:HTTP 请求
├── verbose - 详细:详细信息
├── debug - 调试:调试信息
└── silly - 最低:最详细的信息二、NestJS 内置日志系统
2.1 内置日志示例
启动应用时的日志:
# 正常日志(黄色)
[Nest] 12345 - 2024/01/01, 10:00:00 LOG [NestApplication] Nest application successfully started
# 错误日志(红色)
[Nest] 12345 - 2024/01/01, 10:00:00 ERROR [NestApplication] Port 3000 is already in use2.2 关闭日志
src/main.ts:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: false, // 关闭所有日志
});
await app.listen(3000);
}
bootstrap();2.3 使用内置 Logger 类
src/app.controller.ts:
import { Controller, Get } from '@nestjs/common';
import { Logger } from '@nestjs/common';
@Controller()
export class AppController {
private logger = new Logger('AppModule'); // 设置模块名称
@Get()
getHello() {
// 不同级别的日志
this.logger.log('这是一条 log 信息');
this.logger.error('这是一条 error 错误');
this.logger.warn('这是一条 warn 警告');
this.logger.verbose('这是一条 verbose 详细信息');
this.logger.debug('这是一条 debug 调试信息');
return 'Hello World';
}
}日志输出:
[AppModule] 这是一条 log 信息
[AppModule] 这是一条 error 错误
[AppModule] 这是一条 warn 警告
[AppModule] 这是一条 verbose 详细信息
[AppModule] 这是一条 debug 调试信息2.4 自定义日志级别
src/main.ts:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Logger, LogLevel } from '@nestjs/common';
async function bootstrap() {
// 只显示 error 和 warn 级别的日志
const app = await NestFactory.create(AppModule, {
logger: ['error', 'warn', 'log'] as LogLevel[],
});
await app.listen(3000);
}
bootstrap();三、Winston 日志库集成
3.1 Winston 简介
Winston 特点:
Winston 优势:
│
├── 丰富的传输方式(Transports)
│ ├── Console(控制台)
│ ├── File(文件)
│ ├── Daily Rotate File(滚动文件)
│ ├── HTTP(远程服务器)
│ └── 自定义传输
│
├── 灵活的格式化
│ ├── Timestamp(时间戳)
│ ├── JSON 格式
│ ├── 自定义格式
│ └── 颜色高亮
│
├── 多日志级别
│ ├── error、warn、info
│ ├── http、verbose、debug
│ └── silly
│
└── 集中化管理
├── 统一配置
├── 多输出目标
└── 日志分类存储3.2 安装依赖
# 安装 nest-winston 和 winston
pnpm install nest-winston winston
# 或使用 npm
npm install nest-winston winston
# 安装滚动日志插件
pnpm install winston-daily-rotate-file
# 安装环境变量工具
pnpm install cross-env依赖说明:
| 包名 | 作用 |
|---|---|
nest-winston | NestJS 的 Winston 集成模块 |
winston | Winston 核心库 |
winston-daily-rotate-file | 滚动日志文件插件 |
cross-env | 跨平台环境变量设置 |
3.3 基础集成
src/main.ts:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 替换 NestJS 默认日志为 Winston
app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));
await app.listen(3000);
}
bootstrap();src/app.module.ts:
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
@Module({
imports: [
WinstonModule.forRoot({
transports: [
// 控制台输出
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.simple(),
),
}),
],
}),
],
})
export class AppModule {}3.4 在 Service 中使用
src/app.controller.ts:
import { Controller, Get } from '@nestjs/common';
import { Logger } from '@nestjs/common';
import { Inject } from '@nestjs/common';
@Controller()
export class AppController {
constructor(@Inject(Logger) private readonly logger: Logger) {}
@Get()
getHello() {
this.logger.log('这是一条 log 信息');
this.logger.error('这是一条 error 错误');
this.logger.warn('这是一条 warn 警告');
this.logger.verbose('这是一条 verbose 详细信息');
this.logger.debug('这是一条 debug 调试信息');
return 'Hello World';
}
}四、Winston 高级配置
4.1 格式化配置
src/app.module.ts:
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
@Module({
imports: [
WinstonModule.forRoot({
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), // 时间戳
winston.format.ms(), // 毫秒
winston.format.colorize({ all: true }), // 颜色
winston.format.simple(), // 简单格式
),
}),
],
}),
],
})
export class AppModule {}格式化函数说明:
| 函数 | 作用 | 示例 |
|---|---|---|
timestamp() | 添加时间戳 | 2024-01-01 10:00:00 |
ms() | 添加毫秒 | +123ms |
colorize() | 颜色高亮 | 不同级别不同颜色 |
simple() | 简单格式 | info: message {"timestamp":"..."} |
json() | JSON 格式 | {"level":"info","message":"..."} |
prettyPrint() | 美化输出 | 多行 JSON |
printf() | 自定义格式 | info: 2024-01-01 message |
4.2 自定义格式
import * as winston from 'winston';
const customFormat = winston.format.printf(({ level, message, timestamp, context }) => {
return `${timestamp} [${context || 'App'}] ${level}: ${message}`;
});
@Module({
imports: [
WinstonModule.forRoot({
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.colorize(),
customFormat,
),
}),
],
}),
],
})
export class AppModule {}输出示例:
2024-01-01 10:00:00 [AppController] info: 这是一条 log 信息
2024-01-01 10:00:00 [AppController] error: 这是一条 error 错误4.3 日志级别设置
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
@Module({
imports: [
WinstonModule.forRoot({
level: 'silly', // 设置最低日志级别
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.simple(),
),
}),
],
}),
],
})
export class AppModule {}五、文件日志存储
5.1 基础文件存储
src/app.module.ts:
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
@Module({
imports: [
WinstonModule.forRoot({
transports: [
// 控制台输出
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.simple(),
),
}),
// 错误日志文件
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
}),
// 所有日志文件
new winston.transports.File({
filename: 'logs/combined.log',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
}),
],
}),
],
})
export class AppModule {}生成的文件:
logs/
├── error.log # 只记录 error 级别
└── combined.log # 记录所有级别5.2 滚动日志文件
为什么需要滚动日志:
滚动日志解决的问题:
│
├── 文件过大
│ ├── 单个文件难以打开
│ ├── 查找效率低下
│ └── 占用大量磁盘空间
│
├── 日志管理
│ ├── 按日期分类
│ ├── 自动清理过期日志
│ └── 压缩归档
│
└── 性能优化
├── 减少单个文件大小
├── 提高写入效率
└── 便于备份恢复安装依赖:
pnpm install winston-daily-rotate-filesrc/app.module.ts:
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';
// 创建滚动日志传输器
const createDailyRotateTransport = (level: string, filename: string) => {
return new DailyRotateFile({
level,
filename: `logs/${filename}-%DATE%.log`,
datePattern: 'YYYY-MM-DD-HH',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.simple(),
),
});
};
@Module({
imports: [
WinstonModule.forRoot({
transports: [
// 控制台输出
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.simple(),
),
}),
// 错误日志滚动文件
createDailyRotateTransport('error', 'error'),
// 所有日志滚动文件
createDailyRotateTransport('info', 'app'),
],
}),
],
})
export class AppModule {}配置参数说明:
| 参数 | 类型 | 作用 | 示例 |
|---|---|---|---|
filename | string | 文件名模式 | logs/error-%DATE%.log |
datePattern | string | 日期模式 | YYYY-MM-DD-HH |
zippedArchive | boolean | 是否压缩归档 | true |
maxSize | string | 最大文件大小 | 20m(20MB) |
maxFiles | string | 最大保存时间 | 14d(14天) |
level | string | 日志级别 | error、info |
生成的文件:
logs/
├── error-2024-01-01-10.log.gz # 压缩的错误日志
├── error-2024-01-01-11.log.gz
├── app-2024-01-01-10.log.gz # 压缩的应用日志
└── app-2024-01-01-11.log.gz5.3 日志格式对比
JSON 格式(适合日志分析):
{
"level": "info",
"message": "User logged in",
"timestamp": "2024-01-01T10:00:00.000Z",
"context": "AuthService",
"userId": 123,
"ip": "192.168.1.1"
}Simple 格式(适合人类阅读):
2024-01-01 10:00:00 info: User logged in {"context":"AuthService","userId":123}自定义格式(最灵活):
[2024-01-01 10:00:00] [AuthService] INFO: User logged in (userId: 123)六、环境区分配置
6.1 使用 cross-env 设置环境变量
安装 cross-env:
pnpm install cross-envpackage.json:
{
"scripts": {
"start:dev": "cross-env NODE_ENV=development nest start --watch",
"start": "cross-env NODE_ENV=development nest start",
"start:prod": "cross-env NODE_ENV=production node dist/main",
"build": "cross-env NODE_ENV=production nest build"
}
}6.2 根据环境配置日志
src/app.module.ts:
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';
// 判断是否为开发环境
const isDev = process.env.NODE_ENV === 'development';
// 创建滚动日志传输器
const createDailyRotateTransport = (level: string, filename: string) => {
return new DailyRotateFile({
level,
filename: `logs/${filename}-%DATE%.log`,
datePattern: 'YYYY-MM-DD-HH',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.simple(),
),
});
};
@Module({
imports: [
WinstonModule.forRoot({
level: isDev ? 'silly' : 'info', // 开发环境显示所有日志,生产环境只显示 info 以上
transports: [
// 控制台输出(开发和生产都启用)
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.simple(),
),
}),
// 文件日志(仅生产环境启用)
...(!isDev
? [
createDailyRotateTransport('error', 'error'),
createDailyRotateTransport('info', 'app'),
]
: []),
],
}),
],
})
export class AppModule {}配置逻辑:
环境配置策略:
│
├── 开发环境(NODE_ENV=development)
│ ├── 日志级别:silly(所有日志)
│ ├── 输出目标:控制台
│ └── 不生成日志文件
│
└── 生产环境(NODE_ENV=production)
├── 日志级别:info(info 以上)
├── 输出目标:控制台 + 文件
└── 滚动日志文件(自动清理)6.3 环境变量配置文件
.env.development:
NODE_ENV=development
LOG_LEVEL=silly.env.production:
NODE_ENV=production
LOG_LEVEL=infosrc/app.module.ts:
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: [`.env.${process.env.NODE_ENV || 'development'}`],
}),
WinstonModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
const isDev = configService.get('NODE_ENV') === 'development';
const logLevel = configService.get('LOG_LEVEL') || 'info';
return {
level: logLevel,
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.simple(),
),
}),
// ... 其他配置
],
};
},
}),
],
})
export class AppModule {}七、日志最佳实践
7.1 日志内容规范
好的日志应该包含:
// 好的日志示例
this.logger.log(`User ${userId} logged in from ${ip}`);
this.logger.error(`Database connection failed: ${error.message}`, error.stack);
this.logger.warn(`API rate limit exceeded for user ${userId}`);
this.logger.debug(`Request body: ${JSON.stringify(body)}`);
// 不好的日志示例
this.logger.log('User logged in'); // 缺少上下文
this.logger.error('Error'); // 没有错误详情
this.logger.warn('Something wrong'); // 没有具体信息日志分类:
日志分类:
│
├── 业务日志
│ ├── 用户登录/登出
│ ├── 订单创建/支付
│ └── 关键业务操作
│
├── 系统日志
│ ├── 应用启动/关闭
│ ├── 数据库连接
│ └── 缓存操作
│
├── 错误日志
│ ├── 未捕获异常
│ ├── 数据库错误
│ └── 第三方服务错误
│
└── 调试日志
├── 变量值
├── 执行流程
└── 性能数据7.2 日志中间件
src/common/middleware/logger.middleware.ts:
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { Logger } from '@nestjs/common';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
private logger = new Logger('HTTP');
use(req: Request, res: Response, next: NextFunction) {
const { method, originalUrl } = req;
const startTime = Date.now();
res.on('finish', () => {
const { statusCode } = res;
const responseTime = Date.now() - startTime;
this.logger.log(
`${method} ${originalUrl} ${statusCode} - ${responseTime}ms`
);
});
next();
}
}在模块中应用:
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
@Module({})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('*');
}
}7.3 异常过滤器日志
src/common/filters/http-exception.filter.ts:
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { Logger } from '@nestjs/common';
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(HttpExceptionFilter.name);
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message =
exception instanceof HttpException
? exception.getResponse()
: 'Internal server error';
// 记录错误日志
this.logger.error(
`${request.method} ${request.url} - ${status} - ${JSON.stringify(message)}`,
exception instanceof Error ? exception.stack : undefined
);
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message,
});
}
}7.4 性能监控日志
src/common/interceptors/logging.interceptor.ts:
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Logger } from '@nestjs/common';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private logger = new Logger('Performance');
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { method, url } = request;
const startTime = Date.now();
return next.handle().pipe(
tap(() => {
const responseTime = Date.now() - startTime;
if (responseTime > 1000) {
// 超过 1 秒的慢请求
this.logger.warn(
`Slow request: ${method} ${url} took ${responseTime}ms`
);
} else {
this.logger.debug(`${method} ${url} took ${responseTime}ms`);
}
})
);
}
}八、完整配置示例
8.1 项目结构
project/
├── src/
│ ├── common/
│ │ ├── filters/
│ │ │ └── http-exception.filter.ts
│ │ ├── interceptors/
│ │ │ └── logging.interceptor.ts
│ │ └── middleware/
│ │ └── logger.middleware.ts
│ ├── config/
│ │ └── winston.config.ts
│ ├── app.module.ts
│ └── main.ts
├── logs/ # 日志文件目录(生产环境)
│ ├── error-2024-01-01.log.gz
│ └── app-2024-01-01.log.gz
├── .env.development
├── .env.production
└── package.json8.2 Winston 配置文件
src/config/winston.config.ts:
import * as winston from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';
const isDev = process.env.NODE_ENV === 'development';
// 创建滚动日志传输器
const createDailyRotateTransport = (level: string, filename: string) => {
return new DailyRotateFile({
level,
filename: `logs/${filename}-%DATE%.log`,
datePattern: 'YYYY-MM-DD-HH',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.simple(),
),
});
};
// Winston 配置
export const winstonConfig = {
level: isDev ? 'silly' : 'info',
transports: [
// 控制台输出
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.colorize({ all: true }),
winston.format.simple(),
),
}),
// 文件日志(仅生产环境)
...(!isDev
? [
createDailyRotateTransport('error', 'error'),
createDailyRotateTransport('info', 'app'),
]
: []),
],
};8.3 AppModule 配置
src/app.module.ts:
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import { winstonConfig } from './config/winston.config';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
@Module({
imports: [WinstonModule.forRoot(winstonConfig)],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('*');
}
}8.4 main.ts 配置
src/main.ts:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
import { LoggingInterceptor } from './common/interceptors/logging.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 使用 Winston 日志
app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));
// 全局异常过滤器
app.useGlobalFilters(new HttpExceptionFilter());
// 全局拦截器
app.useGlobalInterceptors(new LoggingInterceptor());
await app.listen(3000);
}
bootstrap();九、常见问题与解决方案
9.1 日志文件过大
问题:单个日志文件过大,难以打开和查找
解决方案:
// 使用滚动日志文件
new DailyRotateFile({
maxSize: '20m', // 最大 20MB
maxFiles: '14d', // 保留 14 天
zippedArchive: true, // 自动压缩
});9.2 日志级别不正确
问题:debug 日志在生产环境输出
解决方案:
// 根据环境设置日志级别
const isDev = process.env.NODE_ENV === 'development';
const winstonConfig = {
level: isDev ? 'silly' : 'info', // 生产环境只显示 info 以上
// ...
};9.3 日志丢失上下文
问题:日志缺少关键信息,难以定位问题
解决方案:
// 添加上下文信息
this.logger.error(
`User login failed: userId=${userId}, ip=${ip}`,
error.stack,
'AuthService'
);9.4 日志格式不统一
问题:不同模块日志格式不一致
解决方案:
// 使用统一的格式化函数
const logFormat = winston.format.printf(({ level, message, timestamp, context }) => {
return `${timestamp} [${context || 'App'}] ${level}: ${message}`;
});十、学习要点总结
10.1 核心概念速记
NestJS 日志系统核心概念:
│
├── 内置日志
│ ├── Logger 类
│ ├── 5 个日志级别:log、error、warn、verbose、debug
│ └── 可关闭或自定义日志级别
│
├── Winston 集成
│ ├── nest-winston 模块
│ ├── 替换默认日志:app.useLogger()
│ ├── 多种 transports:Console、File、DailyRotateFile
│ └── 灵活的格式化:timestamp、colorize、simple
│
├── 文件日志
│ ├── 基础文件:File transport
│ ├── 滚动日志:winston-daily-rotate-file
│ └── 自动清理:maxSize、maxFiles
│
└── 环境区分
├── cross-env 设置环境变量
├── 开发环境:控制台 + 所有日志
└── 生产环境:控制台 + 文件 + info 以上日志10.2 重点知识清单
| 知识点 | 重要程度 | 掌握程度 |
|---|---|---|
| 日志的作用和级别 | 未掌握 / 已掌握 | |
| NestJS 内置 Logger | 未掌握 / 已掌握 | |
| Winston 基础集成 | 未掌握 / 已掌握 | |
| Winston 格式化 | 未掌握 / 已掌握 | |
| 文件日志存储 | 未掌握 / 已掌握 | |
| 滚动日志配置 | 未掌握 / 已掌握 | |
| 环境区分配置 | 未掌握 / 已掌握 | |
| 日志中间件 | 未掌握 / 已掌握 |
10.3 课后思考题
- 为什么需要日志系统?它有哪些作用?
- NestJS 内置的日志级别有哪些?分别用于什么场景?
- 如何集成 Winston 到 NestJS?
- 什么是滚动日志?为什么需要滚动日志?
- 如何根据环境区分日志配置?