Golang实现类似Sentry的本地错误监控系统
3 回复
感谢您的提问。
过去我使用 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
}
这些方案提供了不同复杂度的本地错误监控实现,可以根据具体需求选择合适的方案或组合使用。

