最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go語言編寫高可用日志收集腳本

 更新時間:2025年09月10日 08:49:54   作者:程序員愛釣魚  
在分布式系統(tǒng)和微服務(wù)架構(gòu)中,日志是排查問題、審計行為、監(jiān)控狀態(tài)的重要來源,本文將編寫一個輕量級,高可用的日志收集腳本,有需要的小伙伴可以了解下

在分布式系統(tǒng)和微服務(wù)架構(gòu)中,日志是排查問題、審計行為、監(jiān)控狀態(tài)的重要來源。本篇作為《Go語言100個實戰(zhàn)案例》中的一篇,帶你從設(shè)計到實現(xiàn),完整寫出一個輕量級、高可用的日志收集腳本(Agent),能夠?qū)崟r采集多個本地日志文件、處理文件切割(rotation)、按批發(fā)送到遠(yuǎn)端聚合服務(wù),并具備重試、限流和優(yōu)雅停止能力。

目標(biāo)與場景

目標(biāo):實現(xiàn)一個“可部署到每臺機器上”的日志采集腳本(agent),功能包括:

  • 監(jiān)控并 tail 多個指定日志文件(支持通配符)
  • 處理日志切割(rotation)場景(無需丟失數(shù)據(jù))
  • 將日志按批次、JSON 格式發(fā)送到遠(yuǎn)端 HTTP 接收端(可替換為 Kafka/gRPC)
  • 支持并發(fā)、限流、指數(shù)退避重試和本地緩沖
  • 可優(yōu)雅停止并保證數(shù)據(jù)盡可能送達(dá)

適用場景:小型到中型集群的輕量采集、調(diào)試環(huán)境、或作為自研日志管道的一部分。

技術(shù)選型(簡要)

  • 語言:Go(并發(fā)模型天然適合)
  • 文件 tail:github.com/hpcloud/tail(成熟、支持 rotation)——也可用 fsnotify + 自實現(xiàn) tail,但 hpcloud/tail 工具成熟、代碼量少
  • 網(wǎng)絡(luò)傳輸:HTTP POST + gzip + JSON(易于接入)
  • 配置:命令行 flags + 簡單 JSON/YAML(本文用 flags)
  • 重試策略:指數(shù)退避(帶上限)

注:示例使用 hpcloud/tail 來可靠處理文件 truncation/rotation,實際生產(chǎn)可替換為更復(fù)雜的 offset 存儲(保證斷點續(xù)傳)

項目結(jié)構(gòu)(示意)

log-agent/
├─ main.go
├─ sender.go
├─ tailer.go
├─ go.mod

下面直接給出一個 單文件(main.go)  的可運行示例,方便快速理解與使用。

完整代碼(main.go)

// main.go
package main

import (
    "bufio"
    "bytes"
    "compress/gzip"
    "context"
    "encoding/json"
    "flag"
    "fmt"
    "io"
    "net/http"
    "os"
    "os/signal"
    "path/filepath"
    "sync"
    "syscall"
    "time"

    "github.com/hpcloud/tail"
)

// LogRecord 定義發(fā)送到服務(wù)器的 JSON 結(jié)構(gòu)
type LogRecord struct {
    Timestamp time.Time `json:"timestamp"`
    Host      string    `json:"host"`
    Path      string    `json:"path"`
    Line      string    `json:"line"`
}

// Config
var (
    globPattern = flag.String("paths", "/var/log/*.log", "日志文件路徑,支持通配符")
    endpoint    = flag.String("endpoint", "http://127.0.0.1:8080/ingest", "日志收集服務(wù)地址")
    batchSize   = flag.Int("batch", 200, "每次發(fā)送最大條數(shù)")
    batchWait   = flag.Duration("wait", 2*time.Second, "批量發(fā)送最大等待時間")
    workers     = flag.Int("workers", 4, "并發(fā)發(fā)送 worker 數(shù)")
    maxQueue    = flag.Int("queue", 2000, "本地隊列最大條數(shù),超出丟棄最老")
)

func main() {
    flag.Parse()

    host, _ := os.Hostname()

    paths, err := filepath.Glob(*globPattern)
    if err != nil {
        fmt.Fprintf(os.Stderr, "invalid pattern: %v\n", err)
        os.Exit(1)
    }
    if len(paths) == 0 {
        fmt.Fprintf(os.Stderr, "no logs matched pattern: %s\n", *globPattern)
        os.Exit(1)
    }

    // context for graceful shutdown
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // signal handling
    sigc := make(chan os.Signal, 1)
    signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
    go func() {
        <-sigc
        fmt.Println("received shutdown signal, stopping...")
        cancel()
    }()

    // central channel for lines
    lineCh := make(chan LogRecord, *maxQueue)

    var wg sync.WaitGroup

    // start tailers
    for _, p := range paths {
        wg.Add(1)
        go func(path string) {
            defer wg.Done()
            if err := tailFile(ctx, path, host, lineCh); err != nil {
                fmt.Fprintf(os.Stderr, "tail %s error: %v\n", path, err)
            }
        }(p)
    }

    // start sender workers
    senderWg := &sync.WaitGroup{}
    for i := 0; i < *workers; i++ {
        senderWg.Add(1)
        go func(id int) {
            defer senderWg.Done()
            runSender(ctx, id, lineCh, *endpoint, *batchSize, *batchWait)
        }(i)
    }

    // wait for tailers to finish (on ctx cancel they will exit)
    wg.Wait()
    // close channel to signal senders to flush and exit
    close(lineCh)
    // wait for senders to finish
    senderWg.Wait()

    fmt.Println("agent stopped")
}

// tailFile 使用 hpcloud/tail 跟蹤文件
func tailFile(ctx context.Context, path, host string, out chan<- LogRecord) error {
    cfg := tail.Config{
        Follow:    true,
        ReOpen:    true, // 支持日志切割后重新打開
        MustExist: false,
        Poll:      true,
        Logger:    tail.DiscardingLogger,
    }
    t, err := tail.TailFile(path, cfg)
    if err != nil {
        return err
    }
    defer t.Cleanup()

    for {
        select {
        case <-ctx.Done():
            t.Cleanup()
            return nil
        case line, ok := <-t.Lines:
            if !ok {
                // channel closed; end
                return nil
            }
            if line == nil {
                continue
            }
            rec := LogRecord{
                Timestamp: time.Now().UTC(),
                Host:      host,
                Path:      path,
                Line:      line.Text,
            }
            // non-blocking send to avoid blocking tail; drop oldest if full
            select {
            case out <- rec:
            default:
                // drop one and push new (simple policy)
                select {
                case <-out:
                default:
                }
                select {
                case out <- rec:
                default:
                    // give up if still full
                }
            }
        }
    }
}

// runSender 聚合并發(fā)送日志,帶簡單重試
func runSender(ctx context.Context, id int, in <-chan LogRecord, endpoint string, batchSize int, batchWait time.Duration) {
    httpClient := &http.Client{
        Timeout: 10 * time.Second,
    }
    buf := make([]LogRecord, 0, batchSize)

    sendBatch := func(batch []LogRecord) error {
        if len(batch) == 0 {
            return nil
        }
        // marshal
        data, err := json.Marshal(batch)
        if err != nil {
            return err
        }
        // gzip body
        var b bytes.Buffer
        gw := gzip.NewWriter(&b)
        if _, err := gw.Write(data); err != nil {
            _ = gw.Close()
            return err
        }
        _ = gw.Close()

        req, _ := http.NewRequest("POST", endpoint, &b)
        req.Header.Set("Content-Encoding", "gzip")
        req.Header.Set("Content-Type", "application/json")
        // retry with exponential backoff
        var attempt int
        for {
            attempt++
            resp, err := httpClient.Do(req)
            if err == nil {
                io.Copy(io.Discard, resp.Body)
                resp.Body.Close()
                if resp.StatusCode >= 200 && resp.StatusCode < 300 {
                    return nil
                }
                err = fmt.Errorf("bad status: %s", resp.Status)
            }
            // on ctx done, abort immediately
            select {
            case <-ctx.Done():
                return fmt.Errorf("context canceled")
            default:
            }
            if attempt >= 5 {
                return err
            }
            // backoff
            sleep := time.Duration(500*(1<<uint(attempt-1))) * time.Millisecond
            if sleep > 10*time.Second {
                sleep = 10 * time.Second
            }
            time.Sleep(sleep)
        }
    }

    timer := time.NewTimer(batchWait)
    defer timer.Stop()

    for {
        select {
        case <-ctx.Done():
            // flush remaining
            _ = sendBatch(buf)
            return
        case rec, ok := <-in:
            if !ok {
                // channel closed -> flush and exit
                _ = sendBatch(buf)
                return
            }
            buf = append(buf, rec)
            if len(buf) >= batchSize {
                _ = sendBatch(buf)
                buf = buf[:0]
                if !timer.Stop() {
                    select {
                    case <-timer.C:
                    default:
                    }
                }
                timer.Reset(batchWait)
            }
        case <-timer.C:
            if len(buf) > 0 {
                _ = sendBatch(buf)
                buf = buf[:0]
            }
            timer.Reset(batchWait)
        }
    }
}

使用方法

1.初始化模塊并獲取依賴:

go mod init example.com/log-agent
go get github.com/hpcloud/tail
go build -o log-agent main.go

2.運行(示例):

./log-agent -paths "/var/log/myapp/*.log" -endpoint "http://log-collector:8080/ingest" -batch 100 -workers 4

3.建議把 agent 用 systemd 管理或容器化部署為 DaemonSet(K8s)或 sidecar。

實踐要點與注意事項

日志切割:使用 ReOpen: true 可處理 logrotate 產(chǎn)生的新文件句柄;生產(chǎn)環(huán)境建議結(jié)合 inode 校驗與持久化 offset(例如把 offset 存到本地文件或 SQLite)以支持重啟斷點續(xù)傳。

傳輸安全:生產(chǎn)環(huán)境使用 HTTPS + 鑒權(quán)(API Key / mTLS)來防止日志被竊取或篡改。

后端吞吐:發(fā)送端需要限流與批次控制,避免短時間內(nèi)把流量拉爆目標(biāo)端。也可以使用本地磁盤隊列(如 diskqueue)在網(wǎng)絡(luò)中斷時持久化緩存。

結(jié)構(gòu)化日志:盡量讓應(yīng)用輸出結(jié)構(gòu)化 JSON 日志,這樣聚合與查詢更強。若是 plain text,可在 agent 處做簡單解析(regex)或轉(zhuǎn)發(fā)原始行。

監(jiān)控與自檢:給 agent 加入心跳/metrics(Prometheus)接口,監(jiān)控發(fā)送失敗數(shù)、隊列長度等關(guān)鍵指標(biāo)。

日志隱私:注意日志中可能包含敏感數(shù)據(jù)(PII、密碼、token),可在 agent 端進(jìn)行脫敏或過濾再上報。

進(jìn)一步改進(jìn)(思路)

  • 使用持久化隊列(disk-backed)保證斷網(wǎng)或進(jìn)程崩潰后不丟日志。
  • 支持多種傳輸后端:Kafka、gRPC、AWS S3、Elasticsearch 等。
  • 支持日志標(biāo)簽(service、env、pod)自動注入(從系統(tǒng) / 環(huán)境變量獲?。?。
  • 增加插件化解析器(nginx、app custom parser)做字段抽取。
  • 通過 Web UI 或配置中心動態(tài)下發(fā)采集規(guī)則。

總結(jié)

這篇文章展示了如何用 Go 快速實現(xiàn)一個可靠、可擴展的日志收集腳本:從文件采集、切割處理,到批量發(fā)送與重試策略,都給出了實際可運行的示例代碼。實現(xiàn)中充分利用了 Go 的并發(fā)、channel 與 context,代碼簡潔、易擴展。把這個 agent 打包部署在每臺節(jié)點上,就能為后端日志聚合系統(tǒng)提供穩(wěn)定可靠的數(shù)據(jù)源。

到此這篇關(guān)于Go語言編寫高可用日志收集腳本的文章就介紹到這了,更多相關(guān)Go日志收集內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang中Delve版本太低無法Debug的問題

    Golang中Delve版本太低無法Debug的問題

    這篇文章主要介紹了Golang中Delve版本太低無法Debug的問題,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11
  • go語言string轉(zhuǎn)結(jié)構(gòu)體的實現(xiàn)

    go語言string轉(zhuǎn)結(jié)構(gòu)體的實現(xiàn)

    本文主要介紹了go語言string轉(zhuǎn)結(jié)構(gòu)體的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • Golang使用Gin創(chuàng)建Restful API的實現(xiàn)

    Golang使用Gin創(chuàng)建Restful API的實現(xiàn)

    本文主要介紹了Golang使用Gin創(chuàng)建Restful API的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • go語言中如何使用select的實現(xiàn)示例

    go語言中如何使用select的實現(xiàn)示例

    本文主要介紹了go語言中如何使用select的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • Go網(wǎng)絡(luò)編程TCP抓包實操示例探究

    Go網(wǎng)絡(luò)編程TCP抓包實操示例探究

    作為一名軟件開發(fā)者,網(wǎng)絡(luò)編程是必備知識,本文通過?Go?語言實現(xiàn)?TCP?套接字編程,并結(jié)合?tcpdump?工具,展示它的三次握手、數(shù)據(jù)傳輸以及四次揮手的過程,幫助讀者更好地理解?TCP?協(xié)議與?Go?網(wǎng)絡(luò)編程
    2024-01-01
  • Golang依賴注入工具digo的使用詳解

    Golang依賴注入工具digo的使用詳解

    這篇文章主要為大家詳細(xì)介紹了Golang中依賴注入工具digo的使用,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-06-06
  • Go語言實現(xiàn)高并發(fā)百萬QPS的關(guān)鍵技術(shù)棧

    Go語言實現(xiàn)高并發(fā)百萬QPS的關(guān)鍵技術(shù)棧

    Go語言憑借其輕量級goroutine和高效的調(diào)度器,天然適合高并發(fā)場景,Go語言通過Goroutine調(diào)度優(yōu)化、I/O多路復(fù)用等技術(shù)實現(xiàn)高并發(fā),結(jié)合性能調(diào)優(yōu)與架構(gòu)設(shè)計,可支撐百萬QPS,延遲低于5ms,支持未來擴展
    2025-05-05
  • GPT回答go語言和C語言map操作方法對比

    GPT回答go語言和C語言map操作方法對比

    這篇文章主要為大家介紹了GPT回答go語言和C語言map操作方法對比,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • Go設(shè)計模式之狀態(tài)模式講解和代碼示例

    Go設(shè)計模式之狀態(tài)模式講解和代碼示例

    狀態(tài)是一種行為設(shè)計模式,?讓你能在一個對象的內(nèi)部狀態(tài)變化時改變其行為,該模式將與狀態(tài)相關(guān)的行為抽取到獨立的狀態(tài)類中,?讓原對象將工作委派給這些類的實例,?而不是自行進(jìn)行處理,本文將通過代碼示例給大家簡單的介紹一下Go狀態(tài)模式
    2023-08-08
  • Go ORM的封裝解決方式詳解

    Go ORM的封裝解決方式詳解

    這篇文章主要為大家介紹了Go ORM的封裝解決方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01

最新評論

房山区| 永昌县| 怀宁县| 永福县| 镶黄旗| 禄劝| 彩票| 河源市| 玉山县| 若羌县| 体育| 吴堡县| 灵璧县| 清镇市| 吕梁市| 洞头县| 鹰潭市| 吴桥县| 德昌县| 平原县| 芜湖县| 安阳县| 枞阳县| 宁南县| 靖江市| 荔浦县| 泽库县| 平潭县| 仙居县| 尖扎县| 白城市| 松江区| 长治市| 舒城县| 当阳市| 炎陵县| 南木林县| 玛纳斯县| 汉阴县| 郧西县| 光山县|