Golang实现类似Sentry的本地错误监控系统

Golang实现类似Sentry的本地错误监控系统 有两种非本地部署的错误报告方式:

如何在本地部署环境中进行错误报告?

3 回复

你的要求是什么?

更多关于Golang实现类似Sentry的本地错误监控系统的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢您的提问。

过去我使用 Python 和 Sentry。

这对于理解处理 HTTP 请求失败时发生了什么非常有帮助。

我希望有一个能显示堆栈跟踪的 HTML 页面。并且,如果我想查看详细信息,我应该能够展开某一行,并看到该行的变量及其值。

在堆栈跟踪旁边,我希望看到 HTTP 请求信息(URL、方法、请求头)。

下一个功能是:在 Go 代码中,我希望能够显式地创建这样的跟踪。如果遇到一种奇怪的情况,但我的代码可以继续运行,我希望发出一个警告,其中包含与上述相同的信息。

希望这描绘出了一个可以想象的画面。如果您需要更多细节,请告诉我。

在本地部署环境中实现错误监控,可以通过以下方案构建:

方案一:基于日志文件的本地监控系统

package localerror

import (
    "encoding/json"
    "fmt"
    "os"
    "path/filepath"
    "runtime"
    "sync"
    "time"
)

type ErrorReport struct {
    Error      string                 `json:"error"`
    StackTrace string                 `json:"stack_trace"`
    Context    map[string]interface{} `json:"context"`
    Timestamp  time.Time              `json:"timestamp"`
    Service    string                 `json:"service"`
    Level      string                 `json:"level"` // error, warning, info
}

type LocalErrorMonitor struct {
    logDir     string
    service    string
    maxFiles   int
    fileSizeMB int64
    mu         sync.Mutex
}

func NewLocalErrorMonitor(logDir, service string) *LocalErrorMonitor {
    os.MkdirAll(logDir, 0755)
    return &LocalErrorMonitor{
        logDir:     logDir,
        service:    service,
        maxFiles:   10,
        fileSizeMB: 10,
    }
}

func (m *LocalErrorMonitor) CaptureError(err error, context map[string]interface{}) {
    report := ErrorReport{
        Error:      err.Error(),
        StackTrace: getStackTrace(),
        Context:    context,
        Timestamp:  time.Now(),
        Service:    m.service,
        Level:      "error",
    }
    
    m.saveReport(report)
}

func (m *LocalErrorMonitor) CaptureMessage(message string, level string, context map[string]interface{}) {
    report := ErrorReport{
        Error:      message,
        StackTrace: getStackTrace(),
        Context:    context,
        Timestamp:  time.Now(),
        Service:    m.service,
        Level:      level,
    }
    
    m.saveReport(report)
}

func (m *LocalErrorMonitor) saveReport(report ErrorReport) {
    m.mu.Lock()
    defer m.mu.Unlock()
    
    // 获取当前日志文件
    logFile := filepath.Join(m.logDir, fmt.Sprintf("errors_%s.json", time.Now().Format("2006-01-02")))
    
    // 检查文件大小
    if info, err := os.Stat(logFile); err == nil {
        if info.Size() > m.fileSizeMB*1024*1024 {
            // 文件过大,创建新的日志文件
            logFile = filepath.Join(m.logDir, 
                fmt.Sprintf("errors_%s_%d.json", 
                    time.Now().Format("2006-01-02"),
                    time.Now().Unix()))
        }
    }
    
    // 写入日志
    data, _ := json.MarshalIndent(report, "", "  ")
    f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return
    }
    defer f.Close()
    
    f.Write(data)
    f.WriteString("\n")
    
    // 清理旧文件
    m.cleanupOldFiles()
}

func getStackTrace() string {
    buf := make([]byte, 1024)
    n := runtime.Stack(buf, false)
    return string(buf[:n])
}

func (m *LocalErrorMonitor) cleanupOldFiles() {
    files, err := filepath.Glob(filepath.Join(m.logDir, "errors_*.json"))
    if err != nil {
        return
    }
    
    if len(files) > m.maxFiles {
        // 按修改时间排序并删除最旧的文件
        // 这里简化处理,实际需要按时间排序
        for i := 0; i < len(files)-m.maxFiles; i++ {
            os.Remove(files[i])
        }
    }
}

方案二:集成HTTP服务器的实时监控

package localerror

import (
    "encoding/json"
    "net/http"
    "sync"
    "time"
)

type WebMonitor struct {
    errors     []ErrorReport
    mu         sync.RWMutex
    maxErrors  int
    httpServer *http.Server
}

func NewWebMonitor(port string) *WebMonitor {
    monitor := &WebMonitor{
        maxErrors: 1000,
        errors:    make([]ErrorReport, 0),
    }
    
    // 启动HTTP服务器
    mux := http.NewServeMux()
    mux.HandleFunc("/errors", monitor.handleErrors)
    mux.HandleFunc("/health", monitor.handleHealth)
    
    monitor.httpServer = &http.Server{
        Addr:    ":" + port,
        Handler: mux,
    }
    
    go func() {
        monitor.httpServer.ListenAndServe()
    }()
    
    return monitor
}

func (m *WebMonitor) CaptureError(err error, context map[string]interface{}) {
    report := ErrorReport{
        Error:      err.Error(),
        StackTrace: getStackTrace(),
        Context:    context,
        Timestamp:  time.Now(),
        Level:      "error",
    }
    
    m.mu.Lock()
    defer m.mu.Unlock()
    
    m.errors = append(m.errors, report)
    
    // 保持错误数量不超过最大值
    if len(m.errors) > m.maxErrors {
        m.errors = m.errors[len(m.errors)-m.maxErrors:]
    }
}

func (m *WebMonitor) handleErrors(w http.ResponseWriter, r *http.Request) {
    m.mu.RLock()
    defer m.mu.RUnlock()
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(m.errors)
}

func (m *WebMonitor) handleHealth(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("OK"))
}

// 使用示例
func main() {
    // 初始化本地监控
    monitor := NewWebMonitor("8080")
    
    // 模拟错误
    err := fmt.Errorf("database connection failed")
    context := map[string]interface{}{
        "user_id": 12345,
        "action":  "process_payment",
    }
    
    monitor.CaptureError(err, context)
    
    // 通过浏览器访问 http://localhost:8080/errors 查看错误报告
}

方案三:集成Prometheus和Grafana的监控方案

package localerror

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
    "github.com/prometheus/client_golang/prometheus/promhttp"
    "net/http"
)

type PrometheusMonitor struct {
    errorCounter *prometheus.CounterVec
    errorGauge   prometheus.Gauge
}

func NewPrometheusMonitor() *PrometheusMonitor {
    errorCounter := promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "application_errors_total",
            Help: "Total number of application errors",
        },
        []string{"service", "type", "severity"},
    )
    
    errorGauge := promauto.NewGauge(
        prometheus.GaugeOpts{
            Name: "application_errors_current",
            Help: "Current number of active errors",
        },
    )
    
    // 启动Prometheus metrics端点
    http.Handle("/metrics", promhttp.Handler())
    go http.ListenAndServe(":9090", nil)
    
    return &PrometheusMonitor{
        errorCounter: errorCounter,
        errorGauge:   errorGauge,
    }
}

func (m *PrometheusMonitor) CaptureError(err error, service, errorType, severity string) {
    m.errorCounter.WithLabelValues(service, errorType, severity).Inc()
    m.errorGauge.Inc()
    
    // 可以同时写入日志文件
    logErrorToFile(err, map[string]interface{}{
        "service":  service,
        "type":     errorType,
        "severity": severity,
    })
}

// 使用示例
func main() {
    monitor := NewPrometheusMonitor()
    
    // 监控错误
    monitor.CaptureError(
        fmt.Errorf("connection timeout"),
        "payment-service",
        "network",
        "high",
    )
    
    // 通过Prometheus收集指标,Grafana展示
}

方案四:使用SQLite存储错误数据

package localerror

import (
    "database/sql"
    "time"
    _ "github.com/mattn/go-sqlite3"
)

type SQLiteMonitor struct {
    db *sql.DB
}

func NewSQLiteMonitor(dbPath string) (*SQLiteMonitor, error) {
    db, err := sql.Open("sqlite3", dbPath)
    if err != nil {
        return nil, err
    }
    
    // 创建错误表
    createTable := `
    CREATE TABLE IF NOT EXISTS errors (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        error TEXT NOT NULL,
        stack_trace TEXT,
        context TEXT,
        service TEXT,
        level TEXT,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    );
    CREATE INDEX IF NOT EXISTS idx_errors_created_at ON errors(created_at);
    CREATE INDEX IF NOT EXISTS idx_errors_service ON errors(service);
    `
    
    _, err = db.Exec(createTable)
    if err != nil {
        return nil, err
    }
    
    return &SQLiteMonitor{db: db}, nil
}

func (m *SQLiteMonitor) CaptureError(err error, context map[string]interface{}, service, level string) error {
    contextJSON, _ := json.Marshal(context)
    
    _, err = m.db.Exec(
        `INSERT INTO errors (error, stack_trace, context, service, level, created_at) 
         VALUES (?, ?, ?, ?, ?, ?)`,
        err.Error(),
        getStackTrace(),
        string(contextJSON),
        service,
        level,
        time.Now(),
    )
    
    return err
}

func (m *SQLiteMonitor) GetRecentErrors(limit int) ([]ErrorReport, error) {
    rows, err := m.db.Query(
        `SELECT error, stack_trace, context, service, level, created_at 
         FROM errors ORDER BY created_at DESC LIMIT ?`,
        limit,
    )
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    var reports []ErrorReport
    for rows.Next() {
        var report ErrorReport
        var contextStr string
        
        rows.Scan(&report.Error, &report.StackTrace, &contextStr, 
                  &report.Service, &report.Level, &report.Timestamp)
        
        json.Unmarshal([]byte(contextStr), &report.Context)
        reports = append(reports, report)
    }
    
    return reports, nil
}

这些方案提供了不同复杂度的本地错误监控实现,可以根据具体需求选择合适的方案或组合使用。

回到顶部