topfans/backend/docs/日志架构设计.md
2026-04-07 22:29:48 +08:00

10 KiB
Raw Blame History

微服务日志架构设计

一、设计原则

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格式包含服务名

实现方式

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

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

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

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

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格式

{
  "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
    ↓
日志文件(本地存储)
    ↓
日志收集AgentFilebeat/Fluentd
    ↓
日志聚合系统ELK Stack / Grafana Loki
    ↓
可视化查询Kibana / Grafana

5.2 实现方式

  1. 本地文件存储:每个服务将日志写入本地文件
  2. 日志收集Agent使用Filebeat或Fluentd收集日志
  3. 日志聚合发送到Elasticsearch或Loki
  4. 可视化查询通过Kibana或Grafana查询

优势

  • 各服务独立,不依赖外部系统
  • 即使日志收集系统故障,本地仍有日志
  • 统一的查询界面,可以跨服务查询

六、环境变量配置

6.1 各服务独立配置

每个服务可以通过环境变量独立配置:

# 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示例

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 - 微服务架构总体设计