diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index c676db1..09691ae 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -9,11 +9,40 @@ on: - 'DEV-VERSION' jobs: + prepare: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: 加载版本号 + run: | + if [ -f DEV-VERSION ]; then + echo "VERSION=$(cat DEV-VERSION)" >> $GITHUB_ENV + else + echo "DEV-VERSION file not found!" && exit 1 + fi + - name: 输出版本号 + run: | + echo "Version: ${{ env.VERSION }}" + - name: 预先创建Pre-release + id: create_release + uses: ncipollo/release-action@v1 + with: + name: ${{ env.VERSION }} + artifacts: ./DEV-VERSION + token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ env.VERSION }} + allowUpdates: true + prerelease: true + body: ${{ env.VERSION }} + env: + export PATH: $PATH:/usr/local/go/bin + build: runs-on: ubuntu-latest + needs: prepare strategy: matrix: - goos: [linux] + goos: [linux, darwin, freebsd] goarch: [amd64, arm64] env: OUTPUT_BINARY: ghproxy @@ -64,6 +93,7 @@ jobs: tag: ${{ env.VERSION }} allowUpdates: true prerelease: true + body: ${{ env.VERSION }} env: export PATH: $PATH:/usr/local/go/bin diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d3d4e10..c8e41df 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,11 +9,39 @@ on: - 'VERSION' jobs: + prepare: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: 加载版本号 + run: | + if [ -f VERSION ]; then + echo "VERSION=$(cat VERSION)" >> $GITHUB_ENV + else + echo "VERSION file not found!" && exit 1 + fi + - name: 输出版本号 + run: | + echo "Version: ${{ env.VERSION }}" + - name: 预先创建release + id: create_release + uses: ncipollo/release-action@v1 + with: + name: ${{ env.VERSION }} + artifacts: ./VERSION + token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ env.VERSION }} + allowUpdates: true + body: ${{ env.VERSION }} + env: + export PATH: $PATH:/usr/local/go/bin + build: runs-on: ubuntu-latest + needs: prepare # 确保这个作业在 prepare 作业完成后运行 strategy: matrix: - goos: [linux] + goos: [linux, darwin, freebsd] goarch: [amd64, arm64] env: OUTPUT_BINARY: ghproxy @@ -60,6 +88,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} tag: ${{ env.VERSION }} allowUpdates: true + body: ${{ env.VERSION }} env: export PATH: $PATH:/usr/local/go/bin diff --git a/CHANGELOG.md b/CHANGELOG.md index 56c983f..727c05f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # 更新日志 +2.1.0 +--- +- RELEASE: v2.1.0正式版发布; +- CHANGE: 加入`FreeBSD`与`Darwin`系统支持 +- CHANGE: 更新安全政策, v1和24w版本序列生命周期正式结束 +- ADD: 加入`timing`中间件记录响应时间 +- ADD: 加入`loggin`中间件包装日志输出 +- CHANGE: 更新logger版本至v1.3.0 +- CHANGE: 改进日志相关 +- ADD: 加入日志等级配置项 + +25w12d +--- +- PRE-RELEASE: 此版本是v2.1.0的预发布版本,请勿在生产环境中使用; +- CHANGE: 处理类型断言相关问题 + +25w12c +--- +- PRE-RELEASE: 此版本是v2.1.0的预发布版本,请勿在生产环境中使用; +- CHANGE: 加入`FreeBSD`与`Darwin`系统支持 + +25w12b +--- +- PRE-RELEASE: 此版本是v2.0.8/v2.1.0的预发布版本,请勿在生产环境中使用; +- ADD: 加入`timing`中间件记录响应时间 +- ADD: 加入`loggin`中间件包装日志输出 +- CHANGE: 更新安全政策, v1和24w版本序列生命周期正式结束 + +25w12a +--- +- PRE-RELEASE: 此版本是v2.0.8/v2.1.0的预发布版本,请勿在生产环境中使用; +- CHANGE: 更新logger版本至v1.3.0 +- CHANGE: 改进日志相关 +- ADD: 加入日志等级配置项 + 2.0.7 --- - RELEASE: v2.0.7正式版发布; diff --git a/DEV-VERSION b/DEV-VERSION index 9548754..0be1651 100644 --- a/DEV-VERSION +++ b/DEV-VERSION @@ -1 +1 @@ -25w11a +25w12d \ No newline at end of file diff --git a/README.md b/README.md index 131a8e4..229da37 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ staticPath = "/data/www" # 静态页面文件路径 [log] logFilePath = "/data/ghproxy/log/ghproxy.log" # 日志文件路径 maxLogSize = 5 # MB 日志文件最大大小 +level = "info" # 日志级别 dump, debug, info, warn, error, none [cors] enabled = true # 是否开启跨域 diff --git a/SECURITY.MD b/SECURITY.MD index 8fc021c..5ac050f 100644 --- a/SECURITY.MD +++ b/SECURITY.MD @@ -6,8 +6,10 @@ | 版本 | 是否支持 | | --- | --- | -| v1.x.x | :white_check_mark: | -| 24w*a/b/c... | :warning: 此为PRE-RELEASE版本,用于开发与测试,可能存在未知的问题 | +| v2.x.x | :white_check_mark: 当前最新版本序列, 受支持 | +| v1.x.x | :x: 这些版本已结束生命周期,不再受支持 | +| 25w*a/b/c... | :warning: 此为PRE-RELEASE版本,用于开发与测试,可能存在未知的问题 | +| 24w*a/b/c... | :warning: 此为PRE-RELEASE版本,用于开发与测试,可能存在未知的问题 生命周期已完全结束 | | v0.x.x | :x: 这些版本不再受支持 | ### 用户须知 diff --git a/VERSION b/VERSION index 6a0ca2d..50aea0e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.7 \ No newline at end of file +2.1.0 \ No newline at end of file diff --git a/api/api.go b/api/api.go index 2216787..9aae5f6 100644 --- a/api/api.go +++ b/api/api.go @@ -15,6 +15,8 @@ var ( var ( logw = logger.Logw + LogDump = logger.LogDump + logDebug = logger.LogDebug logInfo = logger.LogInfo logWarning = logger.LogWarning logError = logger.LogError diff --git a/auth/auth-header.go b/auth/auth-header.go index 5089254..4cdff18 100644 --- a/auth/auth-header.go +++ b/auth/auth-header.go @@ -13,7 +13,7 @@ func AuthHeaderHandler(c *gin.Context, cfg *config.Config) (isValid bool, err st } // 获取"GH-Auth"的值 authToken := c.GetHeader("GH-Auth") - logInfo("%s %s %s %s %s AUTH_TOKEN: %s", c.Request.Method, c.Request.Host, c.Request.URL.Path, c.Request.Proto, c.Request.RemoteAddr, authToken) + logDebug("%s %s %s %s %s AUTH_TOKEN: %s", c.Request.Method, c.Request.Host, c.Request.URL.Path, c.Request.Proto, c.Request.RemoteAddr, authToken) if authToken == "" { err := "Auth Header == nil" return false, err @@ -25,6 +25,5 @@ func AuthHeaderHandler(c *gin.Context, cfg *config.Config) (isValid bool, err st return false, err } - logInfo("auth SUCCESS: %t", isValid) return isValid, "" } diff --git a/auth/auth-parameters.go b/auth/auth-parameters.go index c14e23a..20d1ac2 100644 --- a/auth/auth-parameters.go +++ b/auth/auth-parameters.go @@ -13,7 +13,7 @@ func AuthParametersHandler(c *gin.Context, cfg *config.Config) (isValid bool, er } authToken := c.Query("auth_token") - logInfo("%s %s %s %s %s AUTH_TOKEN: %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto, authToken) + logDebug("%s %s %s %s %s AUTH_TOKEN: %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto, authToken) if authToken == "" { err := "Auth token == nil" @@ -26,6 +26,5 @@ func AuthParametersHandler(c *gin.Context, cfg *config.Config) (isValid bool, er return false, err } - logInfo("auth SUCCESS: %t", isValid) return isValid, "" } diff --git a/auth/auth.go b/auth/auth.go index 643661a..0705b8d 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -9,6 +9,8 @@ import ( var ( logw = logger.Logw + LogDump = logger.LogDump + logDebug = logger.LogDebug logInfo = logger.LogInfo logWarning = logger.LogWarning logError = logger.LogError @@ -21,7 +23,7 @@ func Init(cfg *config.Config) { if cfg.Whitelist.Enabled { LoadWhitelist(cfg) } - logInfo("Auth Init") + logDebug("Auth Init") } func AuthHandler(c *gin.Context, cfg *config.Config) (isValid bool, err string) { @@ -32,10 +34,10 @@ func AuthHandler(c *gin.Context, cfg *config.Config) (isValid bool, err string) isValid, err = AuthHeaderHandler(c, cfg) return isValid, err } else if cfg.Auth.AuthMethod == "" { - logWarning("Auth method not set") + logError("Auth method not set") return true, "" } else { - logWarning("Auth method not supported") + logError("Auth method not supported") return false, "Auth method not supported" } } diff --git a/config/config.go b/config/config.go index ea325a4..de72a4b 100644 --- a/config/config.go +++ b/config/config.go @@ -31,6 +31,7 @@ type PagesConfig struct { type LogConfig struct { LogFilePath string `toml:"logFilePath"` MaxLogSize int `toml:"maxLogSize"` + Level string `toml:"level"` } type CORSConfig struct { diff --git a/config/config.toml b/config/config.toml index 5d1900f..d981545 100644 --- a/config/config.toml +++ b/config/config.toml @@ -12,6 +12,7 @@ staticDir = "/data/www" [log] logFilePath = "/data/ghproxy/log/ghproxy.log" maxLogSize = 5 # MB +level = "info" # dump, debug, info, warn, error, none [cors] enabled = true diff --git a/deploy/config.toml b/deploy/config.toml index 6053f12..ec684ec 100644 --- a/deploy/config.toml +++ b/deploy/config.toml @@ -8,6 +8,7 @@ debug = false [pages] enabled = false staticDir = "/usr/local/ghproxy/pages" +level = "info" [log] logFilePath = "/usr/local/ghproxy/log/ghproxy.log" diff --git a/go.mod b/go.mod index cdd0660..6d5d294 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23.6 require ( github.com/BurntSushi/toml v1.4.0 - github.com/WJQSERVER-STUDIO/go-utils/logger v1.2.0 + github.com/WJQSERVER-STUDIO/go-utils/logger v1.3.0 github.com/gin-gonic/gin v1.10.0 golang.org/x/time v0.10.0 ) @@ -30,10 +30,10 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.14.0 // indirect - golang.org/x/crypto v0.32.0 // indirect + golang.org/x/crypto v0.33.0 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect - google.golang.org/protobuf v1.36.4 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1f28606..3669a1b 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/WJQSERVER-STUDIO/go-utils/logger v1.2.0 h1:6JkiVGRasfdBxEV8Qnv5x00Bq4qX1Tg6f58Ar/D2YX0= -github.com/WJQSERVER-STUDIO/go-utils/logger v1.2.0/go.mod h1:oW884JCCPDU6c906LI0uKXndWLiRvjb9LkGYC2cqRO8= +github.com/WJQSERVER-STUDIO/go-utils/logger v1.3.0 h1:rOvutC4zYfvtSGN2CNZrycjtq8dLpfu7ypy7tTEErPY= +github.com/WJQSERVER-STUDIO/go-utils/logger v1.3.0/go.mod h1:oW884JCCPDU6c906LI0uKXndWLiRvjb9LkGYC2cqRO8= github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs= github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= @@ -69,8 +69,8 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4= golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -80,8 +80,8 @@ golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/loggin/loggin.go b/loggin/loggin.go new file mode 100644 index 0000000..ccc66be --- /dev/null +++ b/loggin/loggin.go @@ -0,0 +1,34 @@ +package loggin + +import ( + "ghproxy/timing" + "time" + + "github.com/WJQSERVER-STUDIO/go-utils/logger" + "github.com/gin-gonic/gin" +) + +var ( + logw = logger.Logw + LogDump = logger.LogDump + logDebug = logger.LogDebug + logInfo = logger.LogInfo + logWarning = logger.LogWarning + logError = logger.LogError +) + +// 日志中间件 +func Middleware() gin.HandlerFunc { + return func(c *gin.Context) { + // 处理请求 + c.Next() + + var timingResults time.Duration + + // 获取计时结果 + timingResults, _ = timing.Get(c) + + // 记录日志 IP METHOD URL USERAGENT PROTOCOL STATUS TIMING + logInfo("%s %s %s %s %d %s ", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Writer.Status(), timingResults) + } +} diff --git a/main.go b/main.go index 36be801..9bd6faa 100644 --- a/main.go +++ b/main.go @@ -6,15 +6,16 @@ import ( "fmt" "io" "io/fs" - "log" "net/http" "time" "ghproxy/api" "ghproxy/auth" "ghproxy/config" + "ghproxy/loggin" "ghproxy/proxy" "ghproxy/rate" + "ghproxy/timing" "github.com/WJQSERVER-STUDIO/go-utils/logger" @@ -40,6 +41,8 @@ var ( var ( logw = logger.Logw + LogDump = logger.LogDump + logDebug = logger.LogDebug logInfo = logger.LogInfo logWarning = logger.LogWarning logError = logger.LogError @@ -53,20 +56,27 @@ func loadConfig() { var err error cfg, err = config.LoadConfig(cfgfile) if err != nil { - log.Fatalf("Failed to load config: %v", err) + fmt.Printf("Failed to load config: %v\n", err) + } + if cfg.Server.Debug { + fmt.Println("Config File Path: ", cfgfile) + fmt.Printf("Loaded config: %v\n", cfg) } - fmt.Println("Config File Path: ", cfgfile) - fmt.Printf("Loaded config: %v\n", cfg) } func setupLogger(cfg *config.Config) { var err error err = logger.Init(cfg.Log.LogFilePath, cfg.Log.MaxLogSize) if err != nil { - log.Fatalf("Failed to initialize logger: %v", err) + fmt.Printf("Failed to initialize logger: %v\n", err) } - logInfo("Config File Path: ", cfgfile) - logInfo("Loaded config: %v\n", cfg) + err = logger.SetLogLevel(cfg.Log.Level) + if err != nil { + fmt.Printf("Logger Level Error: %v\n", err) + } + fmt.Printf("Log Level: %s\n", cfg.Log.Level) + logDebug("Config File Path: ", cfgfile) + logDebug("Loaded config: %v\n", cfg) logInfo("Init Completed") } @@ -87,7 +97,6 @@ func setupRateLimit(cfg *config.Config) { } else { logError("Invalid RateLimit Method: %s", cfg.RateLimit.RateMethod) } - logInfo("Rate Limit Loaded") } } @@ -116,9 +125,20 @@ func init() { runMode = "release" } + logDebug("Run Mode: %s", runMode) + gin.LoggerWithWriter(io.Discard) router = gin.New() + + // 添加recovery中间件 router.Use(gin.Recovery()) + + // 添加log中间件 + router.Use(loggin.Middleware()) + + // 添加计时中间件 + router.Use(timing.Middleware()) + //H2C默认值为true,而后遵循cfg.Server.EnableH2C的设置 if cfg.Server.EnableH2C == "on" { router.UseH2C = true @@ -141,7 +161,7 @@ func init() { } else if !cfg.Pages.Enabled { pages, err := fs.Sub(pagesFS, "pages") if err != nil { - log.Fatalf("Failed when processing pages: %s", err) + logError("Failed when processing pages: %s", err) } router.GET("/", gin.WrapH(http.FileServer(http.FS(pages)))) router.GET("/favicon.ico", gin.WrapH(http.FileServer(http.FS(pages)))) @@ -152,6 +172,8 @@ func init() { }) fmt.Printf("GHProxy Version: %s\n", version) + fmt.Printf("A Go Based High-Performance Github Proxy \n") + fmt.Printf("Made by WJQSERVER-STUDIO\n") } func main() { diff --git a/proxy/authpass.go b/proxy/authpass.go index b5eba12..c6d77a2 100644 --- a/proxy/authpass.go +++ b/proxy/authpass.go @@ -11,6 +11,7 @@ func AuthPassThrough(c *gin.Context, cfg *config.Config, req *http.Request) { if cfg.Auth.PassThrough { token := c.Query("token") if token != "" { + logDebug("%s %s %s %s %s Auth-PassThrough: token %s", c.ClientIP(), c.Request.Method, c.Request.URL.String(), c.Request.Header.Get("User-Agent"), c.Request.Proto, token) switch cfg.Auth.AuthMethod { case "parameters": if !cfg.Auth.Enabled { diff --git a/proxy/chunkreq.go b/proxy/chunkreq.go index 44b5759..893b524 100644 --- a/proxy/chunkreq.go +++ b/proxy/chunkreq.go @@ -54,12 +54,11 @@ func initChunkedHTTPClient() { func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, mode string, runMode string) { method := c.Request.Method - logInfo("%s %s %s %s %s", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto) // 发送HEAD请求, 预获取Content-Length headReq, err := http.NewRequest("HEAD", u, nil) if err != nil { - HandleError(c, fmt.Sprintf("创建HEAD请求失败: %v", err)) + HandleError(c, fmt.Sprintf("Failed to create request: %v", err)) return } setRequestHeaders(c, headReq) @@ -108,7 +107,7 @@ func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, mode stri // 创建请求 req, err := http.NewRequest(method, u, bodyReader) if err != nil { - HandleError(c, fmt.Sprintf("创建请求失败: %v", err)) + HandleError(c, fmt.Sprintf("Failed to create request: %v", err)) return } @@ -118,7 +117,7 @@ func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, mode stri resp, err := cclient.Do(req) if err != nil { - HandleError(c, fmt.Sprintf("发送请求失败: %v", err)) + HandleError(c, fmt.Sprintf("Failed to send request: %v", err)) return } defer resp.Body.Close() @@ -171,7 +170,7 @@ func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, mode stri _, err = io.CopyBuffer(c.Writer, resp.Body, buffer) if err != nil { - logError("%s %s %s %s %s 响应复制错误: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err) + logError("%s %s %s %s %s Failed to copy response body: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err) return } else { c.Writer.Flush() // 确保刷入 diff --git a/proxy/gitreq.go b/proxy/gitreq.go index 6dbd20d..568a1ac 100644 --- a/proxy/gitreq.go +++ b/proxy/gitreq.go @@ -38,7 +38,7 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s // 发送HEAD请求, 预获取Content-Length headReq, err := http.NewRequest("HEAD", u, nil) if err != nil { - HandleError(c, fmt.Sprintf("创建HEAD请求失败: %v", err)) + HandleError(c, fmt.Sprintf("Failed to create request: %v", err)) return } setRequestHeaders(c, headReq) @@ -80,7 +80,7 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s // 创建请求 req, err := http.NewRequest(method, u, bodyReader) if err != nil { - HandleError(c, fmt.Sprintf("创建请求失败: %v", err)) + HandleError(c, fmt.Sprintf("Failed to create request: %v", err)) return } setRequestHeaders(c, req) diff --git a/proxy/handler.go b/proxy/handler.go index 81d5690..a052125 100644 --- a/proxy/handler.go +++ b/proxy/handler.go @@ -14,6 +14,7 @@ import ( func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *rate.IPRateLimiter, runMode string) gin.HandlerFunc { return func(c *gin.Context) { + // 限制访问频率 if cfg.RateLimit.Enabled { @@ -54,6 +55,8 @@ func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *ra username, repo := MatchUserRepo(rawPath, cfg, c, matches) // 匹配用户名和仓库名 logInfo("%s %s %s %s %s Matched-Username: %s, Matched-Repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, username, repo) + // dump log 记录详细信息 c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, full Header + LogDump("%s %s %s %s %s %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, c.Request.Header) repouser := fmt.Sprintf("%s/%s", username, repo) // 白名单检查 @@ -83,7 +86,7 @@ func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *ra matches = CheckURL(rawPath, c) if matches == nil { c.AbortWithStatus(http.StatusNotFound) - logError("%s %s %s %s %s 404-NOMATCH", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto) + logWarning("%s %s %s %s %s 404-NOMATCH", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto) return } @@ -91,7 +94,7 @@ func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *ra if exps[5].MatchString(rawPath) { if cfg.Auth.AuthMethod != "header" || !cfg.Auth.Enabled { c.JSON(http.StatusForbidden, gin.H{"error": "HeaderAuth is not enabled."}) - logWarning("%s %s %s %s %s HeaderAuth-Error: HeaderAuth is not enabled.", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto) + logError("%s %s %s %s %s HeaderAuth-Error: HeaderAuth is not enabled.", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto) return } } @@ -110,7 +113,7 @@ func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *ra } // IP METHOD URL USERAGENT PROTO MATCHES - logInfo("%s %s %s %s %s Matches: %v", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, matches) + logDebug("%s %s %s %s %s Matches: %v", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, matches) switch { case exps[0].MatchString(rawPath), exps[1].MatchString(rawPath), exps[3].MatchString(rawPath), exps[4].MatchString(rawPath): diff --git a/proxy/matchrepo.go b/proxy/matchrepo.go index 5a5d000..a074193 100644 --- a/proxy/matchrepo.go +++ b/proxy/matchrepo.go @@ -18,7 +18,7 @@ var ( // 提取用户名和仓库名 func MatchUserRepo(rawPath string, cfg *config.Config, c *gin.Context, matches []string) (string, string) { if gistMatches := gistRegex.FindStringSubmatch(rawPath); len(gistMatches) == 3 { - logInfo("%s %s %s %s %s Matched-Username: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, gistMatches[1]) + LogDump("%s %s %s %s %s Matched-Username: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, gistMatches[1]) return gistMatches[1], "" } // 定义路径 diff --git a/proxy/proxy.go b/proxy/proxy.go index ca45203..3b9d3ff 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -13,6 +13,8 @@ import ( // 日志模块 var ( logw = logger.Logw + LogDump = logger.LogDump + logDebug = logger.LogDebug logInfo = logger.LogInfo logWarning = logger.LogWarning logError = logger.LogError @@ -31,6 +33,7 @@ var exps = []*regexp.Regexp{ func readRequestBody(c *gin.Context) ([]byte, error) { body, err := io.ReadAll(c.Request.Body) if err != nil { + logError("failed to read request body: %v", err) return nil, fmt.Errorf("failed to read request body: %v", err) } defer c.Request.Body.Close() @@ -59,7 +62,7 @@ func SendRequest(c *gin.Context, req *req.Request, method, url string) (*req.Res func HandleError(c *gin.Context, message string) { c.String(http.StatusInternalServerError, fmt.Sprintf("server error %v", message)) - logWarning(message) + logError(message) } func CheckURL(u string, c *gin.Context) []string { @@ -69,7 +72,7 @@ func CheckURL(u string, c *gin.Context) []string { } } errMsg := fmt.Sprintf("%s %s %s %s %s Invalid URL", c.ClientIP(), c.Request.Method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto) - logWarning(errMsg) + logError(errMsg) return nil } diff --git a/rate/rate.go b/rate/rate.go index 85d111e..409b2c1 100644 --- a/rate/rate.go +++ b/rate/rate.go @@ -10,6 +10,8 @@ import ( // 日志输出 var ( logw = logger.Logw + LogDump = logger.LogDump + logDebug = logger.LogDebug logInfo = logger.LogInfo logWarning = logger.LogWarning logError = logger.LogError diff --git a/timing/timing.go b/timing/timing.go new file mode 100644 index 0000000..9c0ada8 --- /dev/null +++ b/timing/timing.go @@ -0,0 +1,86 @@ +package timing + +import ( + "sync" + "time" + + "github.com/gin-gonic/gin" +) + +// 阶段计时结构(固定数组优化) +type timingData struct { + phases [8]struct { // 预分配8个阶段存储 + name string + dur time.Duration + } + count int + start time.Time +} + +// 对象池(内存重用优化) +var pool = sync.Pool{ + New: func() interface{} { + return new(timingData) + }, +} + +// 中间件入口 +func Middleware() gin.HandlerFunc { + return func(c *gin.Context) { + // 从池中获取计时器 + td := pool.Get().(*timingData) + td.start = time.Now() + td.count = 0 + + // 存储到上下文 + c.Set("timing", td) + + // 请求完成后回收对象 + defer func() { + pool.Put(td) + }() + + c.Next() + } +} + +// 记录阶段耗时 +func Record(c *gin.Context, name string) { + if val, exists := c.Get("timing"); exists { + //td := val.(*timingData) + td, ok := val.(*timingData) + if !ok { + return + } + if td.count < len(td.phases) { + td.phases[td.count].name = name + td.phases[td.count].dur = time.Since(td.start) // 直接记录当前时间 + td.count++ + } + } +} + +// 获取计时结果(日志输出用) +func Get(c *gin.Context) (total time.Duration, phases []struct { + Name string + Dur time.Duration +}) { + if val, exists := c.Get("timing"); exists { + //td := val.(*timingData) + td, ok := val.(*timingData) + if !ok { + return + } + for i := 0; i < td.count; i++ { + phases = append(phases, struct { + Name string + Dur time.Duration + }{ + Name: td.phases[i].name, + Dur: td.phases[i].dur, + }) + } + total = time.Since(td.start) + } + return +}