mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 20:40:52 +08:00
feat(logging): integrate logrus with custom Gin middleware for enhanced request logging and recovery
- Added `GinLogrusLogger` for structured request logging using Logrus. - Implemented `GinLogrusRecovery` to handle panics and log stack traces. - Configured log rotation using Lumberjack for efficient log management. - Replaced Gin's default logger and recovery middleware with the custom implementations.
This commit is contained in:
@@ -7,21 +7,27 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/cmd"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/cmd"
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator"
|
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator"
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Version = "dev"
|
Version = "dev"
|
||||||
Commit = "none"
|
Commit = "none"
|
||||||
BuildDate = "unknown"
|
BuildDate = "unknown"
|
||||||
|
logWriter *lumberjack.Logger
|
||||||
|
ginInfoWriter *io.PipeWriter
|
||||||
|
ginErrorWriter *io.PipeWriter
|
||||||
)
|
)
|
||||||
|
|
||||||
// LogFormatter defines a custom log format for logrus.
|
// LogFormatter defines a custom log format for logrus.
|
||||||
@@ -53,18 +59,51 @@ func (m *LogFormatter) Format(entry *log.Entry) ([]byte, error) {
|
|||||||
// It sets up the custom log formatter, enables caller reporting,
|
// It sets up the custom log formatter, enables caller reporting,
|
||||||
// and configures the log output destination.
|
// and configures the log output destination.
|
||||||
func init() {
|
func init() {
|
||||||
// Set logger output to standard output.
|
logDir := "logs"
|
||||||
log.SetOutput(os.Stdout)
|
if err := os.MkdirAll(logDir, 0755); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to create log directory: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
logWriter = &lumberjack.Logger{
|
||||||
|
Filename: filepath.Join(logDir, "main.log"),
|
||||||
|
MaxSize: 10,
|
||||||
|
MaxBackups: 0,
|
||||||
|
MaxAge: 0,
|
||||||
|
Compress: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetOutput(logWriter)
|
||||||
// Enable reporting the caller function's file and line number.
|
// Enable reporting the caller function's file and line number.
|
||||||
log.SetReportCaller(true)
|
log.SetReportCaller(true)
|
||||||
// Set the custom log formatter.
|
// Set the custom log formatter.
|
||||||
log.SetFormatter(&LogFormatter{})
|
log.SetFormatter(&LogFormatter{})
|
||||||
|
|
||||||
|
ginInfoWriter = log.StandardLogger().Writer()
|
||||||
|
gin.DefaultWriter = ginInfoWriter
|
||||||
|
ginErrorWriter = log.StandardLogger().WriterLevel(log.ErrorLevel)
|
||||||
|
gin.DefaultErrorWriter = ginErrorWriter
|
||||||
|
gin.DebugPrintFunc = func(format string, values ...interface{}) {
|
||||||
|
log.StandardLogger().Infof(format, values...)
|
||||||
|
}
|
||||||
|
log.RegisterExitHandler(func() {
|
||||||
|
if logWriter != nil {
|
||||||
|
_ = logWriter.Close()
|
||||||
|
}
|
||||||
|
if ginInfoWriter != nil {
|
||||||
|
_ = ginInfoWriter.Close()
|
||||||
|
}
|
||||||
|
if ginErrorWriter != nil {
|
||||||
|
_ = ginErrorWriter.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// main is the entry point of the application.
|
// main is the entry point of the application.
|
||||||
// It parses command-line flags, loads configuration, and starts the appropriate
|
// It parses command-line flags, loads configuration, and starts the appropriate
|
||||||
// service based on the provided flags (login, codex-login, or server mode).
|
// service based on the provided flags (login, codex-login, or server mode).
|
||||||
func main() {
|
func main() {
|
||||||
|
fmt.Printf("CLIProxyAPI Version: %s, Commit: %s, BuiltAt: %s\n", Version, Commit, BuildDate)
|
||||||
log.Infof("CLIProxyAPI Version: %s, Commit: %s, BuiltAt: %s", Version, Commit, BuildDate)
|
log.Infof("CLIProxyAPI Version: %s, Commit: %s, BuiltAt: %s", Version, Commit, BuildDate)
|
||||||
|
|
||||||
// Command-line flags to control the application's behavior.
|
// Command-line flags to control the application's behavior.
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -44,4 +44,5 @@ require (
|
|||||||
golang.org/x/sys v0.31.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -106,6 +106,8 @@ google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFW
|
|||||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -128,8 +128,8 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add middleware
|
// Add middleware
|
||||||
engine.Use(gin.Logger())
|
engine.Use(logging.GinLogrusLogger())
|
||||||
engine.Use(gin.Recovery())
|
engine.Use(logging.GinLogrusRecovery())
|
||||||
for _, mw := range optionState.extraMiddleware {
|
for _, mw := range optionState.extraMiddleware {
|
||||||
engine.Use(mw)
|
engine.Use(mw)
|
||||||
}
|
}
|
||||||
|
|||||||
65
internal/logging/gin_logger.go
Normal file
65
internal/logging/gin_logger.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"runtime/debug"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GinLogrusLogger writes Gin-style access logs through logrus.
|
||||||
|
func GinLogrusLogger() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
start := time.Now()
|
||||||
|
path := c.Request.URL.Path
|
||||||
|
raw := c.Request.URL.RawQuery
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
if raw != "" {
|
||||||
|
path = path + "?" + raw
|
||||||
|
}
|
||||||
|
|
||||||
|
latency := time.Since(start)
|
||||||
|
if latency > time.Minute {
|
||||||
|
latency = latency.Truncate(time.Second)
|
||||||
|
} else {
|
||||||
|
latency = latency.Truncate(time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode := c.Writer.Status()
|
||||||
|
clientIP := c.ClientIP()
|
||||||
|
method := c.Request.Method
|
||||||
|
errorMessage := c.Errors.ByType(gin.ErrorTypePrivate).String()
|
||||||
|
timestamp := time.Now().Format("2006/01/02 - 15:04:05")
|
||||||
|
logLine := fmt.Sprintf("[GIN] %s | %3d | %13v | %15s | %-7s \"%s\"", timestamp, statusCode, latency, clientIP, method, path)
|
||||||
|
if errorMessage != "" {
|
||||||
|
logLine = logLine + " | " + errorMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case statusCode >= http.StatusInternalServerError:
|
||||||
|
log.Error(logLine)
|
||||||
|
case statusCode >= http.StatusBadRequest:
|
||||||
|
log.Warn(logLine)
|
||||||
|
default:
|
||||||
|
log.Info(logLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GinLogrusRecovery returns a Gin middleware that recovers from panics and logs them via logrus.
|
||||||
|
func GinLogrusRecovery() gin.HandlerFunc {
|
||||||
|
return gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"panic": recovered,
|
||||||
|
"stack": string(debug.Stack()),
|
||||||
|
"path": c.Request.URL.Path,
|
||||||
|
}).Error("recovered from panic")
|
||||||
|
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user