458 lines
10 KiB
Markdown
458 lines
10 KiB
Markdown
# 微服务日志架构设计
|
||
|
||
## 一、设计原则
|
||
|
||
### 1.1 核心原则
|
||
|
||
1. **服务独立性**:每个微服务应该有自己独立的日志实例
|
||
2. **统一配置**:所有服务使用相同的日志配置和格式
|
||
3. **共享工具**:在共享包中提供统一的日志初始化逻辑
|
||
4. **分散部署**:每个服务可以独立配置日志级别和输出方式
|
||
|
||
### 1.2 架构选择
|
||
|
||
**推荐方案**:**每个服务独立,共享logger包**
|
||
|
||
- ✅ 每个服务有独立的logger实例
|
||
- ✅ 所有服务共享 `pkg/logger` 包的初始化逻辑
|
||
- ✅ 统一的日志格式和配置方式
|
||
- ✅ 服务可以独立配置日志级别
|
||
|
||
---
|
||
|
||
## 二、架构设计
|
||
|
||
### 2.1 目录结构
|
||
|
||
```
|
||
backend/
|
||
├── pkg/
|
||
│ └── logger/
|
||
│ └── logger.go # 共享的日志工具包(统一配置)
|
||
├── services/
|
||
│ ├── userService/
|
||
│ │ ├── main.go # 初始化自己的logger实例
|
||
│ │ └── service/
|
||
│ │ └── auth_service.go # 使用logger记录日志
|
||
│ ├── assetService/
|
||
│ │ ├── main.go # 初始化自己的logger实例
|
||
│ │ └── service/
|
||
│ │ └── asset_service.go
|
||
│ ├── socialService/
|
||
│ │ ├── main.go # 初始化自己的logger实例
|
||
│ │ └── service/
|
||
│ │ └── social_service.go
|
||
│ └── ...
|
||
└── gateway/ # API Gateway
|
||
└── main.go # 初始化自己的logger实例
|
||
```
|
||
|
||
### 2.2 日志包设计
|
||
|
||
**文件**:`pkg/logger/logger.go`
|
||
|
||
**设计要点**:
|
||
- 提供统一的初始化函数 `Init()`
|
||
- 每个服务调用 `Init()` 创建自己的logger实例
|
||
- 支持服务级别的配置(服务名、环境等)
|
||
- 统一的日志格式(JSON格式,包含服务名)
|
||
|
||
**实现方式**:
|
||
|
||
```go
|
||
package logger
|
||
|
||
import (
|
||
"os"
|
||
|
||
"go.uber.org/zap"
|
||
"go.uber.org/zap/zapcore"
|
||
)
|
||
|
||
var (
|
||
// Logger 全局logger实例(每个服务独立)
|
||
Logger *zap.Logger
|
||
// Sugar 全局Sugar实例
|
||
Sugar *zap.SugaredLogger
|
||
)
|
||
|
||
// Config 日志配置
|
||
type Config struct {
|
||
ServiceName string // 服务名称(如:user-service, asset-service)
|
||
Environment string // 环境:development 或 production
|
||
LogLevel string // 日志级别:debug, info, warn, error
|
||
LogPath string // 日志文件路径(可选,默认为当前目录下的logs/)
|
||
}
|
||
|
||
// Init 初始化日志(每个服务独立调用)
|
||
func Init(config Config) error {
|
||
var zapConfig zap.Config
|
||
|
||
// 设置服务名作为默认字段
|
||
serviceNameField := zap.String("service", config.ServiceName)
|
||
|
||
// 环境判断
|
||
if config.Environment == "development" {
|
||
// 开发环境:彩色控制台输出
|
||
zapConfig = zap.NewDevelopmentConfig()
|
||
zapConfig.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||
} else {
|
||
// 生产环境:JSON格式,文件输出
|
||
zapConfig = zap.NewProductionConfig()
|
||
|
||
// 设置日志文件路径
|
||
logPath := config.LogPath
|
||
if logPath == "" {
|
||
logPath = "logs"
|
||
}
|
||
|
||
// 创建日志目录
|
||
if err := os.MkdirAll(logPath, 0755); err != nil {
|
||
return err
|
||
}
|
||
|
||
zapConfig.OutputPaths = []string{
|
||
"stdout",
|
||
logPath + "/" + config.ServiceName + ".log",
|
||
}
|
||
zapConfig.ErrorOutputPaths = []string{
|
||
"stderr",
|
||
logPath + "/" + config.ServiceName + "-error.log",
|
||
}
|
||
}
|
||
|
||
// 设置日志级别
|
||
logLevel := config.LogLevel
|
||
if logLevel == "" {
|
||
logLevel = os.Getenv("LOG_LEVEL")
|
||
if logLevel == "" {
|
||
logLevel = "info"
|
||
}
|
||
}
|
||
|
||
var level zapcore.Level
|
||
if err := level.UnmarshalText([]byte(logLevel)); err == nil {
|
||
zapConfig.Level = zap.NewAtomicLevelAt(level)
|
||
}
|
||
|
||
// 构建logger
|
||
var err error
|
||
Logger, err = zapConfig.Build(
|
||
zap.AddCaller(),
|
||
zap.AddStacktrace(zapcore.ErrorLevel),
|
||
zap.Fields(serviceNameField), // 添加服务名字段
|
||
)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 创建Sugar实例
|
||
Sugar = Logger.Sugar()
|
||
|
||
return nil
|
||
}
|
||
|
||
// Sync 刷新日志缓冲区
|
||
func Sync() {
|
||
if Logger != nil {
|
||
_ = Logger.Sync()
|
||
}
|
||
}
|
||
|
||
// GetLogger 获取logger实例
|
||
func GetLogger() *zap.Logger {
|
||
return Logger
|
||
}
|
||
|
||
// GetSugar 获取Sugar实例
|
||
func GetSugar() *zap.SugaredLogger {
|
||
return Sugar
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 三、各服务使用方式
|
||
|
||
### 3.1 User Service(用户与认证服务)
|
||
|
||
**文件**:`services/userService/main.go`
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"flag"
|
||
"os"
|
||
|
||
"github.com/topfans/backend/pkg/database"
|
||
"github.com/topfans/backend/pkg/logger"
|
||
)
|
||
|
||
func main() {
|
||
// 初始化日志(每个服务独立初始化)
|
||
env := os.Getenv("ENV")
|
||
if env == "" {
|
||
env = "development"
|
||
}
|
||
|
||
if err := logger.Init(logger.Config{
|
||
ServiceName: "user-service",
|
||
Environment: env,
|
||
LogLevel: os.Getenv("LOG_LEVEL"),
|
||
}); err != nil {
|
||
panic(err)
|
||
}
|
||
defer logger.Sync()
|
||
|
||
logger.Sugar.Info("Starting User Service...")
|
||
|
||
// 其他初始化代码...
|
||
}
|
||
```
|
||
|
||
**文件**:`services/userService/service/auth_service.go`
|
||
|
||
```go
|
||
package service
|
||
|
||
import (
|
||
"github.com/topfans/backend/pkg/logger"
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
func (s *AuthService) Register(req *pb.RegisterRequest) (*pb.RegisterResponse, error) {
|
||
logger.Logger.Info("Register request received",
|
||
zap.String("mobile", req.Mobile),
|
||
zap.Int64("star_id", req.StarId),
|
||
)
|
||
|
||
// 业务逻辑...
|
||
}
|
||
```
|
||
|
||
### 3.2 Asset Service(资产服务)
|
||
|
||
**文件**:`services/assetService/main.go`
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"github.com/topfans/backend/pkg/logger"
|
||
)
|
||
|
||
func main() {
|
||
// 初始化日志(独立的logger实例)
|
||
if err := logger.Init(logger.Config{
|
||
ServiceName: "asset-service",
|
||
Environment: os.Getenv("ENV"),
|
||
LogLevel: os.Getenv("LOG_LEVEL"),
|
||
}); err != nil {
|
||
panic(err)
|
||
}
|
||
defer logger.Sync()
|
||
|
||
logger.Sugar.Info("Starting Asset Service...")
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### 3.3 API Gateway
|
||
|
||
**文件**:`gateway/main.go`
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"github.com/topfans/backend/pkg/logger"
|
||
)
|
||
|
||
func main() {
|
||
// 初始化日志(独立的logger实例)
|
||
if err := logger.Init(logger.Config{
|
||
ServiceName: "api-gateway",
|
||
Environment: os.Getenv("ENV"),
|
||
LogLevel: os.Getenv("LOG_LEVEL"),
|
||
}); err != nil {
|
||
panic(err)
|
||
}
|
||
defer logger.Sync()
|
||
|
||
logger.Sugar.Info("Starting API Gateway...")
|
||
// ...
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 四、日志格式示例
|
||
|
||
### 4.1 开发环境(控制台)
|
||
|
||
```
|
||
2024-01-01T10:00:00.000+0800 INFO service=user-service Register request received {"mobile": "13800138000", "star_id": 123}
|
||
```
|
||
|
||
### 4.2 生产环境(JSON格式)
|
||
|
||
```json
|
||
{
|
||
"level": "info",
|
||
"ts": 1704067200.000,
|
||
"caller": "auth_service.go:45",
|
||
"msg": "Register request received",
|
||
"service": "user-service",
|
||
"mobile": "13800138000",
|
||
"star_id": 123
|
||
}
|
||
```
|
||
|
||
**优势**:
|
||
- 每个日志都包含 `service` 字段,便于区分是哪个服务的日志
|
||
- JSON格式便于日志收集系统(如ELK、Loki)解析
|
||
- 统一的格式便于统一管理和查询
|
||
|
||
---
|
||
|
||
## 五、日志收集(可选,生产环境)
|
||
|
||
### 5.1 集中式日志收集架构
|
||
|
||
```
|
||
各个服务(独立logger)
|
||
↓
|
||
日志文件(本地存储)
|
||
↓
|
||
日志收集Agent(Filebeat/Fluentd)
|
||
↓
|
||
日志聚合系统(ELK Stack / Grafana Loki)
|
||
↓
|
||
可视化查询(Kibana / Grafana)
|
||
```
|
||
|
||
### 5.2 实现方式
|
||
|
||
1. **本地文件存储**:每个服务将日志写入本地文件
|
||
2. **日志收集Agent**:使用Filebeat或Fluentd收集日志
|
||
3. **日志聚合**:发送到Elasticsearch或Loki
|
||
4. **可视化查询**:通过Kibana或Grafana查询
|
||
|
||
**优势**:
|
||
- 各服务独立,不依赖外部系统
|
||
- 即使日志收集系统故障,本地仍有日志
|
||
- 统一的查询界面,可以跨服务查询
|
||
|
||
---
|
||
|
||
## 六、环境变量配置
|
||
|
||
### 6.1 各服务独立配置
|
||
|
||
每个服务可以通过环境变量独立配置:
|
||
|
||
```bash
|
||
# User Service
|
||
ENV=production
|
||
LOG_LEVEL=info
|
||
LOG_PATH=/var/log/user-service
|
||
|
||
# Asset Service
|
||
ENV=production
|
||
LOG_LEVEL=debug
|
||
LOG_PATH=/var/log/asset-service
|
||
|
||
# API Gateway
|
||
ENV=production
|
||
LOG_LEVEL=warn
|
||
LOG_PATH=/var/log/gateway
|
||
```
|
||
|
||
### 6.2 Docker Compose示例
|
||
|
||
```yaml
|
||
services:
|
||
user-service:
|
||
environment:
|
||
- ENV=production
|
||
- LOG_LEVEL=info
|
||
- LOG_PATH=/var/log
|
||
volumes:
|
||
- ./logs/user-service:/var/log
|
||
|
||
asset-service:
|
||
environment:
|
||
- ENV=production
|
||
- LOG_LEVEL=info
|
||
- LOG_PATH=/var/log
|
||
volumes:
|
||
- ./logs/asset-service:/var/log
|
||
```
|
||
|
||
---
|
||
|
||
## 七、最佳实践
|
||
|
||
### 7.1 日志级别建议
|
||
|
||
| 服务类型 | 推荐级别 | 说明 |
|
||
|---------|---------|------|
|
||
| API Gateway | `warn` | 只记录警告和错误 |
|
||
| User Service | `info` | 记录关键操作 |
|
||
| Asset Service | `info` | 记录关键操作 |
|
||
| Task Service | `debug` | 任务调度需要详细日志 |
|
||
|
||
### 7.2 日志记录原则
|
||
|
||
1. **每个服务独立初始化**:确保服务独立性
|
||
2. **统一的日志格式**:便于统一管理和查询
|
||
3. **包含服务名**:每条日志都标识所属服务
|
||
4. **结构化日志**:使用字段而不是字符串拼接
|
||
5. **敏感信息不记录**:密码、完整Token等
|
||
|
||
### 7.3 性能考虑
|
||
|
||
- 生产环境使用结构化日志(`Logger.Info`)而不是Sugar API
|
||
- 合理设置日志级别,避免过多日志影响性能
|
||
- 使用日志轮转,避免日志文件过大
|
||
|
||
---
|
||
|
||
## 八、总结
|
||
|
||
### 推荐方案
|
||
|
||
✅ **每个服务独立,共享logger包**
|
||
|
||
**优势**:
|
||
1. **服务独立性**:每个服务可以独立部署和配置
|
||
2. **统一格式**:所有服务使用相同的日志格式
|
||
3. **易于管理**:共享的logger包便于维护和升级
|
||
4. **灵活配置**:每个服务可以独立配置日志级别
|
||
5. **便于扩展**:后续可以轻松添加日志收集功能
|
||
|
||
**不推荐**:统一管理所有服务的日志实例
|
||
|
||
**原因**:
|
||
- 破坏了服务的独立性
|
||
- 难以在不同服务中使用不同的日志配置
|
||
- 服务间耦合度增加
|
||
|
||
---
|
||
|
||
## 九、实施步骤
|
||
|
||
1. **第一步**:创建共享的 `pkg/logger/logger.go`
|
||
2. **第二步**:在每个服务的 `main.go` 中初始化logger
|
||
3. **第三步**:在Service层中使用日志记录
|
||
4. **第四步**:(可选)配置日志收集系统
|
||
|
||
---
|
||
|
||
## 十、相关文档
|
||
|
||
- `services/userService/zap日志框架集成指南.md` - 详细的zap集成指南
|
||
- `docs/微服务架构设计.md` - 微服务架构总体设计
|
||
|