【Golang 实战 ELK 日志系统全流程教程(二):Zap 收集日志,并通过 Filebeat 发送】
本文介绍了一个基于微服务的日志采集系统实现方案。系统采用Gin框架构建Web服务,使用Zap日志库实现JSON格式日志记录,支持文件和终端双输出。主要功能包括:1) Gin服务路由配置与优雅退出机制;2) Zap日志中间件记录请求详情;3) 业务日志采集处理。系统通过Docker容器化部署,利用Filebeat实现日志采集转发,配置Harvester和Input组件进行日志文件监控。项目结构清晰,
前言
在微服务架构中,服务日志的采集与处理是运维监控体系的基础。本教程将手把手教你构建一个基础的日志采集系统,包含:
- 使用 Golang Gin 构建 Web 服务
- 使用 Zap 实现 JSON 格式日志记录到文件
- 支持优雅退出,确保日志完整写入
- 使用 Docker 实现服务容器化
- 使用 Filebeat 转发日志到文件(后续可扩展为转发至 ELK/Kafka)
架构分析
项目结构
为更好地展示本章节内容,下面列出项目结构。
demo
│ docker-compose.yml
│ Dockerfile
│ Dockerfile-filebeat
│ go.mod
│ go.sum
│
├─app
│ │ main.exe
│ │ main.go
│ │
│ └─service
│ service.go
│
├─filebeat
│ filebeat.yml
│
├─filebeat_output
│ filebeat.log
│
└─logs
app.log
Gin + Zap 实现日志记录
Gin 服务启动代码主要位于 app/main.go,负责:
- 初始化 Zap 日志组件
- 启动 Gin 引擎与路由
- 设置日志中间件与优雅退出逻辑
package main
import (
"context"
"errors"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
logger := NewLogger()
defer logger.Sync()
server := NewMyServer(logger)
r := gin.New()
r.Use(GinZapLogger(logger), gin.Recovery())
r.GET("/service1", server.Service1)
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
go func() {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Fatal("server error", zap.Error(err))
}
}()
// 优雅退出监听
quit := make(chan os.Signal)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
logger.Info("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
logger.Fatal("Server forced to shutdown:", zap.Error(err))
}
logger.Info("Server exiting")
}
func GinZapLogger(logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
c.Next()
latency := time.Since(start)
status := c.Writer.Status()
clientIP := c.ClientIP()
method := c.Request.Method
logger.Info("GIN_LOG",
zap.Int("status", status),
zap.String("method", method),
zap.String("path", path),
zap.String("ip", clientIP),
zap.Duration("latency", latency),
)
}
}
type MyServer struct {
logger *zap.Logger
}
func NewMyServer(logger *zap.Logger) *MyServer {
return &MyServer{
logger: logger,
}
}
func (s *MyServer) Service1(ctx *gin.Context) {
param := ctx.Query("param")
s.logger.Info("Service1: param", zap.String("param", param))
ctx.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "success",
"data": param,
})
}
func NewLogger() *zap.Logger {
logFile := "logs/app.log"
_ = os.MkdirAll("logs", os.ModePerm)
// 文件输出
fileWriter, _ := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
fileSyncer := zapcore.AddSync(fileWriter)
// 控制台输出
consoleSyncer := zapcore.AddSync(os.Stdout)
// 编码器
encoderCfg := zap.NewProductionEncoderConfig()
encoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder
encoder := zapcore.NewJSONEncoder(encoderCfg)
// 多输出 core
core := zapcore.NewTee(
zapcore.NewCore(encoder, fileSyncer, zapcore.InfoLevel),
zapcore.NewCore(encoder, consoleSyncer, zapcore.DebugLevel),
)
logger := zap.New(core, zap.AddCaller())
logger.Info("Hello Zap!", zap.String("env", "docker"))
return logger
}
功能简述
1. Gin 服务启动与路由
-
创建 Gin 引擎:
gin.New() -
注册中间件:
GinZapLogger(logger):记录每个请求日志gin.Recovery():防止服务崩溃
-
注册路由:
- 示例接口:
GET /service1
- 示例接口:
2. Zap 日志系统初始化
-
使用
NewLogger创建 Zap 日志器,支持:- 日志输出到文件(如:
logs/app.log) - 同时输出到控制台(
os.Stdout) - 日志格式:JSON,时间戳格式为 ISO8601
- 日志等级控制:文件为 Info,控制台为 Debug
- 日志输出到文件(如:
-
使用
zapcore.NewTee实现多路输出 -
日志包含调用代码位置(启用
zap.AddCaller())
3. 日志中间件
-
自定义
GinZapLogger中间件,记录请求详情:- 请求方法、路径、状态码、客户端 IP、处理耗时
- 所有内容写入 Zap 日志
4. 业务逻辑处理
-
示例函数:
MyServer.Service1- 获取 URL 参数
param - 将参数写入日志
- 返回标准 JSON 响应
- 获取 URL 参数
Filebeat 配置详解(filebeat.yml 示例)
Filebeat是一个轻量级的日志采集工具,用于转发和汇总服务器、虚拟机和容器的日志。它从输入源读取日志,通过Harvester逐行读取,然后输出到目标如ElasticSearch。
如图所示,Filebeat 的核心是两个组件:Input 和 Harvester
1. Harvester 说明
- 每个文件由一个 Harvester 负责读取(逐行读取)
- Harvester 负责打开并保持文件描述符
- 即使文件被删除或重命名,Harvester 仍会继续读取内容
- 文件删除后,只要进程持有 FD,内容仍可读取,直到 Harvester 关闭
- 若长时间无数据更新(达到
close_inactive),Harvester 将停止
总结:Harvester 的职责是——逐行读取文件,并将日志传递给 Output。
2. Input 说明
- 管理 Harvester 的组件
- 负责发现哪些文件需要读取(基于路径)
- 支持配置多个 Input,每个 Input 在单独 goroutine 中运行
- Input 发现文件后,为其启动 Harvester 进行日志收集
filebeat.inputs:
- type: log
enabled: true
paths:
- /logs/app.log
output.file:
path: "/output"
filename: "filebeat.log"
rotate_every_kb: 10000
number_of_files: 7
💡 注:paths 要与 Gin 服务日志文件路径保持一致(挂载路径可在 Docker Compose 中设置)。
本章节中我们使用 File 输出作为演示,后续章节中将拓展为输出到 Elasticsearch/Kafka。
启动及编排
我们为 Gin 服务 与 Filebeat 各自编写了独立的 Dockerfile,并通过 docker-compose 统一编排启动。
Gin 服务 Dockerfile
FROM golang:1.22-alpine
WORKDIR /app
# 复制依赖定义,利用缓存优化构建
COPY go.mod go.sum ./
RUN go mod download
# 复制源代码
COPY ./app ./app
RUN go build -o elk ./app/main.go
CMD ["./elk"]
Filebeat Dockerfile
FROM docker.elastic.co/beats/filebeat:7.17.9
USER root
COPY ./filebeat/filebeat.yml /usr/share/filebeat/filebeat.yml
RUN chown filebeat:filebeat /usr/share/filebeat/filebeat.yml && chmod 600 /usr/share/filebeat/filebeat.yml
USER filebeat
Docker Compose 配置
使用 docker-compose.yml 启动两个容器并进行网络/挂载配置:
version: '3'
services:
gin-app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
volumes:
- ./logs:/app/logs # 挂载日志目录
container_name: gin-app
filebeat:
build:
context: .
dockerfile: Dockerfile-filebeat
volumes:
- ./logs:/logs
- ./filebeat_output:/output
depends_on:
- gin-app
container_name: filebeat
启动后系统整体结构:

项目运行说明
1. 构建并启动项目
docker-compose up --build
2. 测试接口请求
curl http://localhost:8080/service1?param=test1
3. 查看 Filebeat 转发后的文件内容
cat ./filebeat_output/filebeat.log
日志示例输出
Gin zap 写入日志文件(app.log):
{
"level": "info",
"ts": "2025-06-24T23:00:56.069+0800",
"caller": "app/main.go:82",
"msg": "Service1: param",
"param": "test1"
}
Filebeat 转发后的日志文件(filebeat.log):
{
"@timestamp": "2025-06-24T13:55:13.408Z",
"@metadata": {
"beat": "filebeat",
"type": "_doc",
"version": "7.17.9"
},
"log": {
"offset": 3637,
"file": {
"path": "/logs/app.log"
}
},
"message": "{\"level\":\"info\",\"ts\":1750773310.5815911,\"msg\":\"Service1: param\",\"param\":\"test1\"}",
"input": {
"type": "log"
},
"ecs": {
"version": "1.12.0"
},
"host": {
"name": "2c889fcbb3a3"
},
"agent": {
"type": "filebeat",
"version": "7.17.9",
"hostname": "2c889fcbb3a3",
"ephemeral_id": "69e67121-a145-4f76-9554-465c198399c2",
"id": "351dc454-38d4-446e-8666-6a665deac048",
"name": "2c889fcbb3a3"
}
}
总结
本章我们完整展示了 Golang + zap + Filebeat 的日志采集方案,并支持 Docker 容器化部署。当前我们已实现:
- 接口日志记录
- Zap 文件日志输出
- Filebeat 读取日志并转发输出
后续我们在此基础上继续拓展:
- 接入 Elasticsearch + Kibana,实现可视化日志分析
参考文档
【Filebeat工作原理分析】
【Go之Zap日志库集成Gin】
【Go 日志记录库:uber-go 的日志操作库 zap 使用】
【docker学习笔记18:Dockerfile 指令 VOLUME 介绍】
【菜鸟教程:docker-compose】
更多推荐

所有评论(0)