forked from DevOps/deploy.stack
feat: 重构日志处理和错误输出格式
refactor: 重构日志处理和错误输出格式 fix: 修正配置加载路径和温度参数类型 docs: 更新结构体字段注释格式
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -18,3 +18,5 @@ tests/
|
||||
# 日志文件
|
||||
*.log
|
||||
logs/
|
||||
mcp_server_go/go.sum
|
||||
mcp_server_go/mcp_server_go
|
||||
|
||||
@@ -11,12 +11,20 @@ import (
|
||||
|
||||
// MCPRequest 定义MCP请求结构
|
||||
type MCPRequest struct {
|
||||
Data interface{} `json:"data"` Type string `json:"type"` Metadata map[string]string `json:"metadata,omitempty"` Timestamp int64 `json:"timestamp"`
|
||||
Data interface{} `json:"data"`
|
||||
Type string `json:"type"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
// MCPResponse 定义MCP响应结构
|
||||
type MCPResponse struct {
|
||||
Success bool `json:"success"` Message string `json:"message,omitempty"` Data interface{} `json:"data,omitempty"` AIResult interface{} `json:"ai_result,omitempty"` RequestID string `json:"request_id,omitempty"` Timestamp int64 `json:"timestamp"`
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
AIResult interface{} `json:"ai_result,omitempty"`
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -24,10 +32,10 @@ func main() {
|
||||
diskData := map[string]interface{}{
|
||||
"disk_id": "sda",
|
||||
"smart_data": map[string]interface{}{
|
||||
"temperature": 38,
|
||||
"power_on_hours": 12345,
|
||||
"read_errors": 0,
|
||||
"write_errors": 0,
|
||||
"temperature": 38,
|
||||
"power_on_hours": 12345,
|
||||
"read_errors": 0,
|
||||
"write_errors": 0,
|
||||
"reallocated_sectors": 0,
|
||||
},
|
||||
"performance_data": map[string]interface{}{
|
||||
@@ -85,8 +93,8 @@ func main() {
|
||||
fmt.Printf(" 成功状态: %v\n", mcpResponse.Success)
|
||||
fmt.Printf(" 消息: %s\n", mcpResponse.Message)
|
||||
fmt.Printf(" 请求ID: %s\n", mcpResponse.RequestID)
|
||||
fmt.Printf(" 时间戳: %d (%s)\n",
|
||||
mcpResponse.Timestamp,
|
||||
fmt.Printf(" 时间戳: %d (%s)\n",
|
||||
mcpResponse.Timestamp,
|
||||
time.Unix(mcpResponse.Timestamp, 0).Format("2006-01-02 15:04:05"))
|
||||
|
||||
// 输出AI分析结果(如果有)
|
||||
@@ -122,4 +130,4 @@ func testHealthCheck() {
|
||||
|
||||
fmt.Printf("健康检查状态码: %d\n", resp.StatusCode)
|
||||
fmt.Printf("健康检查响应: %s\n", string(body))
|
||||
}
|
||||
}
|
||||
@@ -1,107 +1,34 @@
|
||||
module mcp_server_go
|
||||
|
||||
go 1.21
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/prometheus/client_golang v1.18.0
|
||||
github.com/sashabaranov/go-openai v1.17.4
|
||||
github.com/spf13/viper v1.17.0
|
||||
golang.org/x/time v0.5.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/sashabaranov/go-openai v1.41.1
|
||||
github.com/spf13/viper v1.21.0
|
||||
golang.org/x/time v0.13.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/OneOfOne/xxhash v1.2.2 // indirect
|
||||
github.com/alecthomas/kingpin v1.3.1 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.8.0 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/itchyny/gojq v0.12.13 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jessevdk/go-flags v1.5.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.2 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pierrec/lz4 v4.1.17+incompatible // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.18 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.18.0 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/procfs v0.11.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sashabaranov/go-openai v1.17.4 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/afero v1.10.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/cobra v1.7.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.17.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tidwall/gjson v1.17.1 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/toml-lang/toml v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/yosssi/gohtml v0.0.0-20230121090043-345651034a4d // indirect
|
||||
github.com/yosssi/gi v0.0.0-20180507055002-ea47d430d263 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231019153415-0f05c00091f9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231019153415-0f05c00091f9 // indirect
|
||||
google.golang.org/grpc v1.58.3 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -25,42 +26,69 @@ import (
|
||||
|
||||
// Config 结构体用于存储配置
|
||||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"` OpenAI OpenAIConfig `mapstructure:"openai"` Logging LoggingConfig `mapstructure:"logging"` Security SecurityConfig `mapstructure:"security"`
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
OpenAI OpenAIConfig `mapstructure:"openai"`
|
||||
Logging LoggingConfig `mapstructure:"logging"`
|
||||
Security SecurityConfig `mapstructure:"security"`
|
||||
}
|
||||
|
||||
// ServerConfig 服务器配置
|
||||
type ServerConfig struct {
|
||||
ListenAddr string `mapstructure:"listen_addr"` ReadTimeout int `mapstructure:"read_timeout"` WriteTimeout int `mapstructure:"write_timeout"` MaxHeaderBytes int `mapstructure:"max_header_bytes"`
|
||||
ListenAddr string `mapstructure:"listen_addr"`
|
||||
ReadTimeout int `mapstructure:"read_timeout"`
|
||||
WriteTimeout int `mapstructure:"write_timeout"`
|
||||
MaxHeaderBytes int `mapstructure:"max_header_bytes"`
|
||||
}
|
||||
|
||||
// OpenAIConfig OpenAI API配置
|
||||
type OpenAIConfig struct {
|
||||
APIKey string `mapstructure:"api_key"` BaseURL string `mapstructure:"base_url"` Model string `mapstructure:"model"` Temperature float64 `mapstructure:"temperature"` MaxTokens int `mapstructure:"max_tokens"` RequestTimeout int `mapstructure:"request_timeout"`
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
BaseURL string `mapstructure:"base_url"`
|
||||
Model string `mapstructure:"model"`
|
||||
Temperature float64 `mapstructure:"temperature"`
|
||||
MaxTokens int `mapstructure:"max_tokens"`
|
||||
RequestTimeout int `mapstructure:"request_timeout"`
|
||||
}
|
||||
|
||||
// LoggingConfig 日志配置
|
||||
type LoggingConfig struct {
|
||||
Level string `mapstructure:"level"` Format string `mapstructure:"format"` OutputPath string `mapstructure:"output_path"` MaxSize int `mapstructure:"max_size"` MaxAge int `mapstructure:"max_age"` MaxBackups int `mapstructure:"max_backups"` Compress bool `mapstructure:"compress"`
|
||||
Level string `mapstructure:"level"`
|
||||
Format string `mapstructure:"format"`
|
||||
OutputPath string `mapstructure:"output_path"`
|
||||
MaxSize int `mapstructure:"max_size"`
|
||||
MaxAge int `mapstructure:"max_age"`
|
||||
MaxBackups int `mapstructure:"max_backups"`
|
||||
Compress bool `mapstructure:"compress"`
|
||||
}
|
||||
|
||||
// SecurityConfig 安全配置
|
||||
type SecurityConfig struct {
|
||||
AllowedOrigins []string `mapstructure:"allowed_origins"` AllowedMethods []string `mapstructure:"allowed_methods"` AllowedHeaders []string `mapstructure:"allowed_headers"`
|
||||
AllowedOrigins []string `mapstructure:"allowed_origins"`
|
||||
AllowedMethods []string `mapstructure:"allowed_methods"`
|
||||
AllowedHeaders []string `mapstructure:"allowed_headers"`
|
||||
}
|
||||
|
||||
// MCPRequest MCP请求结构体
|
||||
type MCPRequest struct {
|
||||
Data interface{} `json:"data"` Type string `json:"type"` Metadata map[string]string `json:"metadata,omitempty"` Timestamp int64 `json:"timestamp"`
|
||||
Data interface{} `json:"data"`
|
||||
Type string `json:"type"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
// MCPResponse MCP响应结构体
|
||||
type MCPResponse struct {
|
||||
Success bool `json:"success"` Message string `json:"message,omitempty"` Data interface{} `json:"data,omitempty"` AIResult interface{} `json:"ai_result,omitempty"` RequestID string `json:"request_id,omitempty"` Timestamp int64 `json:"timestamp"`
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
AIResult interface{} `json:"ai_result,omitempty"`
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
var (
|
||||
config Config
|
||||
logger *slog.Logger
|
||||
config Config
|
||||
logger *slog.Logger
|
||||
openaiClient *openai.Client
|
||||
// 速率限制器
|
||||
limiter = rate.NewLimiter(rate.Limit(10), 20)
|
||||
@@ -69,7 +97,7 @@ var (
|
||||
prometheus.CounterOpts{
|
||||
Name: "mcp_server_requests_total",
|
||||
Help: "Total number of MCP server requests",
|
||||
},
|
||||
},
|
||||
[]string{"endpoint", "status"},
|
||||
)
|
||||
requestDuration = prometheus.NewHistogramVec(
|
||||
@@ -77,7 +105,7 @@ var (
|
||||
Name: "mcp_server_request_duration_seconds",
|
||||
Help: "Duration of MCP server requests in seconds",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
},
|
||||
},
|
||||
[]string{"endpoint"},
|
||||
)
|
||||
)
|
||||
@@ -95,6 +123,7 @@ func loadConfig() error {
|
||||
viper.AddConfigPath(".")
|
||||
viper.AddConfigPath("/etc/mcp_server/")
|
||||
viper.AddConfigPath("$HOME/.mcp_server/")
|
||||
viper.AddConfigPath("$HOME/.config/mcp_server/")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return fmt.Errorf("读取配置文件失败: %w", err)
|
||||
@@ -149,20 +178,7 @@ func initLogger() error {
|
||||
logLevel = slog.LevelInfo
|
||||
}
|
||||
|
||||
// 创建日志处理器
|
||||
var handler slog.Handler
|
||||
if config.Logging.Format == "json" {
|
||||
handler = slog.NewJSONHandler(logFile, &slog.HandlerOptions{
|
||||
Level: logLevel,
|
||||
})
|
||||
} else {
|
||||
handler = slog.NewTextHandler(logFile, &slog.HandlerOptions{
|
||||
Level: logLevel,
|
||||
})
|
||||
}
|
||||
|
||||
// 同时输出到控制台和文件
|
||||
multiHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel})
|
||||
// 创建日志器,同时输出到控制台和文件
|
||||
logger = slog.New(slog.NewTextHandler(
|
||||
io.MultiWriter(os.Stdout, logFile),
|
||||
&slog.HandlerOptions{Level: logLevel},
|
||||
@@ -225,7 +241,7 @@ func loggingMiddleware(next http.Handler) http.Handler {
|
||||
|
||||
wrappedWriter := &responseWriter{
|
||||
ResponseWriter: w,
|
||||
statusCode: http.StatusOK,
|
||||
statusCode: http.StatusOK,
|
||||
}
|
||||
|
||||
next.ServeHTTP(wrappedWriter, r)
|
||||
@@ -272,9 +288,9 @@ func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"status": "ok",
|
||||
"version": "1.0.0",
|
||||
"timestamp": time.Now().Unix(),
|
||||
"status": "ok",
|
||||
"version": "1.0.0",
|
||||
"timestamp": time.Now().Unix(),
|
||||
"openai_health": oaiHealthy,
|
||||
}
|
||||
|
||||
@@ -296,7 +312,7 @@ func submitHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// 解析请求体
|
||||
var mcpRequest MCPRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&mcpRequest); err != nil {
|
||||
logger.Error("解析请求体失败", slog.String("request_id", requestID), slog.Error(err))
|
||||
logger.Error("解析请求体失败", slog.String("request_id", requestID), slog.Any("error", err))
|
||||
http.Error(w, "无效的请求体", http.StatusBadRequest)
|
||||
requestCounter.WithLabelValues("/mcp/v1/submit", "400").Inc()
|
||||
return
|
||||
@@ -310,7 +326,7 @@ func submitHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// 调用OpenAI API进行分析
|
||||
aIResult, err := analyzeWithOpenAI(r.Context(), mcpRequest, requestID)
|
||||
if err != nil {
|
||||
logger.Error("OpenAI API调用失败", slog.String("request_id", requestID), slog.Error(err))
|
||||
logger.Error("OpenAI API调用失败", slog.String("request_id", requestID), slog.Any("error", err))
|
||||
http.Error(w, "AI分析失败", http.StatusInternalServerError)
|
||||
requestCounter.WithLabelValues("/mcp/v1/submit", "500").Inc()
|
||||
return
|
||||
@@ -329,7 +345,7 @@ func submitHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// 发送响应
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
logger.Error("发送响应失败", slog.String("request_id", requestID), slog.Error(err))
|
||||
logger.Error("发送响应失败", slog.String("request_id", requestID), slog.Any("error", err))
|
||||
}
|
||||
|
||||
requestCounter.WithLabelValues("/mcp/v1/submit", "200").Inc()
|
||||
@@ -371,7 +387,7 @@ func analyzeWithOpenAI(ctx context.Context, request MCPRequest, requestID string
|
||||
Content: prompt,
|
||||
},
|
||||
},
|
||||
Temperature: config.OpenAI.Temperature,
|
||||
Temperature: float32(config.OpenAI.Temperature),
|
||||
MaxTokens: config.OpenAI.MaxTokens,
|
||||
}
|
||||
|
||||
@@ -445,7 +461,7 @@ func main() {
|
||||
// 启动服务器
|
||||
go func() {
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
logger.Error("服务器启动失败", slog.Error(err))
|
||||
logger.Error("服务器启动失败", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
@@ -465,9 +481,9 @@ func main() {
|
||||
|
||||
// 优雅关闭服务器
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
logger.Error("服务器关闭失败", slog.Error(err))
|
||||
logger.Error("服务器关闭失败", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger.Info("MCP服务器已安全关闭")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user