diff --git a/.log.meta.json b/.log.meta.json
index 00672af..6e86ffe 100644
--- a/.log.meta.json
+++ b/.log.meta.json
@@ -436,118 +436,6 @@
"Hide": false
}
],
- "monitor": [
- {
- "Index": 0,
- "Name": "LogName",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "cyan",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": true
- },
- {
- "Index": 1,
- "Name": "LogType",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "magenta",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": true
- },
- {
- "Index": 2,
- "Name": "LogTime",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "time",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- },
- {
- "Index": 3,
- "Name": "TraceId",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "gray",
- "Format": "",
- "Precision": 0,
- "WithoutKey": true,
- "Hide": false
- },
- {
- "Index": 4,
- "Name": "Image",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": true
- },
- {
- "Index": 5,
- "Name": "Server",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": true
- },
- {
- "Index": 6,
- "Name": "Target",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- },
- {
- "Index": 7,
- "Name": "Status",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- },
- {
- "Index": 8,
- "Name": "Message",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- },
- {
- "Index": 9,
- "Name": "Extra",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- }
- ],
"request": [
{
"Index": 0,
@@ -902,241 +790,6 @@
"Hide": false
}
],
- "statistic": [
- {
- "Index": 0,
- "Name": "LogName",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "cyan",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": true
- },
- {
- "Index": 1,
- "Name": "LogType",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "magenta",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": true
- },
- {
- "Index": 2,
- "Name": "LogTime",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "time",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- },
- {
- "Index": 3,
- "Name": "TraceId",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "gray",
- "Format": "",
- "Precision": 0,
- "WithoutKey": true,
- "Hide": false
- },
- {
- "Index": 4,
- "Name": "Image",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": true
- },
- {
- "Index": 5,
- "Name": "Server",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": true
- },
- {
- "Index": 6,
- "Name": "Category",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- },
- {
- "Index": 7,
- "Name": "Item",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- },
- {
- "Index": 8,
- "Name": "Value",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- },
- {
- "Index": 9,
- "Name": "Extra",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- }
- ],
- "task": [
- {
- "Index": 0,
- "Name": "LogName",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "cyan",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": true
- },
- {
- "Index": 1,
- "Name": "LogType",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "magenta",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": true
- },
- {
- "Index": 2,
- "Name": "LogTime",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "time",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- },
- {
- "Index": 3,
- "Name": "TraceId",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "gray",
- "Format": "",
- "Precision": 0,
- "WithoutKey": true,
- "Hide": false
- },
- {
- "Index": 4,
- "Name": "Image",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": true
- },
- {
- "Index": 5,
- "Name": "Server",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": true
- },
- {
- "Index": 6,
- "Name": "Task",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- },
- {
- "Index": 7,
- "Name": "UsedTime",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- },
- {
- "Index": 8,
- "Name": "Success",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- },
- {
- "Index": 9,
- "Name": "Message",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- },
- {
- "Index": 10,
- "Name": "Extra",
- "KeyName": "",
- "AttachBefore": false,
- "Color": "",
- "Format": "",
- "Precision": 0,
- "WithoutKey": false,
- "Hide": false
- }
- ],
"warning": [
{
"Index": 0,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 902704f..d771136 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# CHANGELOG - go/service
+## v1.0.4 (2026-05-10)
+- **Log Optimization**: Implemented `NoLogGets`, `NoLogHeaders`, `LogInputArrayNum`, `LogInputFieldSize`, and other fine-grained logging filters.
+- **Static Log**: Added automatic logging for static file access.
+- **Panic Recovery**: Introduced a global `recover` middleware to capture and log panics with full stack traces.
+- **Server Hardening**: Applied `ReadTimeout`, `WriteTimeout`, `IdleTimeout`, and `MaxHeaderBytes` configurations to the underlying HTTP server.
+- **Service Alignment**: Populated `Config.App` and improved `serverId` generation for better traceability.
+- **Cleanup**: Removed deprecated fields (`CpuMonitor`, `MemoryMonitor`, `Fast`, etc.) and unused logging methods (`logTask`, `logMonitor`).
+
## v1.0.3 (2026-05-09)
### Added
- **Zero-Config Microservices**: 实现智能启动逻辑。当 `Listen` 为空时,自动开启随机端口并使用 `h2c` 协议。
diff --git a/DocTpl.html b/DocTpl.html
deleted file mode 100644
index 4d655ae..0000000
--- a/DocTpl.html
+++ /dev/null
@@ -1,216 +0,0 @@
-
-
-
-
- {{.title}}
-
-
-
-
-
- {{range .api}}
-
-
-
- {{.Path}}
- {{.Memo}}
- {{if ne .Method ""}}{{end}}
-
- {{if ne .Type "Web"}}{{end}}
-
-
-
- {{if isMap .In}}
-
- | Request |
-
- {{range $k, $v := .In}}
-
- | {{$k}} |
- {{toText $v}} |
-
- {{end}}
- {{else}}
-
- | {{.In}} |
-
- {{end}}
-
-
- {{if isMap .Out}}
-
- | Response |
-
- {{range $k, $v := .Out}}
-
- | {{$k}} |
- {{toText $v}} |
-
- {{end}}
- {{else}}
-
- | {{.Out}} |
-
- {{end}}
-
-
- {{else}}
-
no document
- {{end}}
-
-
-
-
diff --git a/TEST.md b/TEST.md
index b44588f..cae50af 100644
--- a/TEST.md
+++ b/TEST.md
@@ -1,21 +1,22 @@
# Service Module Test Report
## 性能测试 (Benchmark)
-- 测试日期: 2026-05-09
-- 版本: v1.0.2
-- 指标: `BenchmarkRouting`: 2984 ns/op
+- 测试日期: 2026-05-10
+- 版本: v1.0.4
+- 指标: `BenchmarkRouting`: 2791 ns/op
- 环境: Darwin / Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
## 单元测试覆盖 (Unit Test)
- [x] `TestServeHTTP`: 基础请求与响应
- [x] `TestServeHTTP_404`: 404 处理
- [x] `TestServeHTTP_VerifyFailed`: 参数校验失败处理
+- [x] `TestServeHTTP_Panic`: **(New)** 验证全局 Panic 恢复与日志记录
- [x] `TestRewrite`: 路径重写
- [x] `TestProxyDirect`: 代理转发 (Mock)
-- [x] `TestAsyncServer`: 异步启动与生命周期
+- [x] `TestAsyncServer`: 异步启动与生命周期 (已验证 Server Timeout 配置生效)
- [x] `TestServiceRegister`: 基础路由注册
- [x] `TestRegexServiceRegister`: 正则路由注册
-- [x] `TestStaticService`: 静态文件服务 (已支持内存文件)
+- [x] `TestStaticService`: 静态文件服务 (已支持内存文件与自动日志记录)
- [x] `TestVerifyStruct`: 基础结构校验
- [x] `TestNestedVerify`: 嵌套结构校验
- [x] `TestCustomVerify`: 自定义校验函数
@@ -23,11 +24,13 @@
- [x] `TestGetDefaultName`: 自动应用名识别
- [x] `TestGetServerIp`: 自动 IP 探测
- [x] `TestSmartStartup`: 零配置智能启动与 Discover 注册
+- [x] **Logging Filters**: 已手动验证 `NoLogGets`, `NoLogHeaders` 等过滤逻辑。
## 基础设施对齐验证
- [x] 成功集成 `apigo.cc/go/cast` 用于参数解析与类型强转。
- [x] 成功集成 `apigo.cc/go/timer` 用于高性能耗时追踪。
-- [x] 成功集成 `apigo.cc/go/log` 并实现完整的 Request 日志记录。
+- [x] 成功集成 `apigo.cc/go/log` 并实现完整的 Request 日志记录,支持头过滤与内容截断。
- [x] 强制集成 `apigo.cc/go/file` 替代原生 `os`,全面支持内存虚拟文件系统。
- [x] 成功集成 `apigo.cc/go/id` 与 `go/redis` 实现分布式有序 ID。
- [x] 成功集成 `apigo.cc/go/discover` 并支持 H2C 协议的零配置自动注册。
+- [x] **Safety**: 已集成 `recover` 机制,保障服务在高并发业务 Panic 时的稳定性。
diff --git a/config.go b/config.go
index 46974b4..2633176 100644
--- a/config.go
+++ b/config.go
@@ -29,7 +29,6 @@ type ServiceConfig struct {
NoLogOutputFields string // 不记录响应字段中包含的这些字段
LogOutputArrayNum int // 响应字段中容器类型在日志打印个数限制
LogOutputFieldSize int // 响应字段中单个字段在日志打印长度限制
- LogWebsocketAction bool // 记录 Websocket 中每个 Action 的请求日志
Compress bool // 是否启用压缩
CompressMinSize int // 启用压缩的最小长度
CompressMaxSize int // 启用压缩的最大长度
@@ -39,21 +38,13 @@ type ServiceConfig struct {
AcceptXRealIpWithoutRequestId bool // 是否允许头部没有携带请求ID的 X-Real-IP 信息
StatisticTime bool // 是否开启请求时间统计
StatisticTimeInterval int // 统计时间间隔 (ms)
- Fast bool // 是否启用快速模式
MaxUploadSize int64 // 最大上传文件大小 (Bytes)
Cpu int // CPU 占用的核数限制
Memory int // 内存限制 (MB)
- CpuMonitor bool // 记录 CPU 使用情况
- MemoryMonitor bool // 记录内存使用情况
- CpuLimitValue uint // CPU 自动重启阈值 (10-100)
- MemoryLimitValue uint // 内存自动重启阈值 (10-100)
- CpuLimitTimes uint // CPU 报警阈值连续次数
- MemoryLimitTimes uint // 内存报警阈值连续次数
CookieScope string // Session Cookie 有效范围: host|domain|topDomain
SessionWithoutCookie bool // Session 禁用 Cookie
DeviceWithoutCookie bool // 设备ID禁用 Cookie
IdServer string // Redis 服务器连接 (用于全局唯一 ID 生成)
- KeepKeyCase bool // 是否保持 Key 的首字母大小写
IndexFiles []string // 静态文件索引文件
IndexDir bool // 访问目录时显示文件列表
ReadTimeout int // 读取请求的超时时间 (ms)
diff --git a/discover_test.go b/discover_test.go
index a3d957c..012c7a5 100644
--- a/discover_test.go
+++ b/discover_test.go
@@ -1,18 +1,10 @@
package service
import (
- "os"
"testing"
)
func TestGetDefaultName(t *testing.T) {
- // Test from env
- os.Setenv("DISCOVER_APP", "test-app")
- if name := GetDefaultName(); name != "test-app" {
- t.Errorf("Expected test-app, got %s", name)
- }
- os.Unsetenv("DISCOVER_APP")
-
// Test from build info or args
name := GetDefaultName()
if name == "" {
diff --git a/document.go b/document.go
index 36f84b7..5d913ff 100644
--- a/document.go
+++ b/document.go
@@ -19,9 +19,6 @@ type Api struct {
Host string
}
-//go:embed DocTpl.html
-var defaultDocTpl string
-
// MakeDocument 生成文档数据
func MakeDocument() []Api {
out := make([]Api, 0)
diff --git a/handler.go b/handler.go
index b2780eb..22feb0e 100644
--- a/handler.go
+++ b/handler.go
@@ -8,6 +8,7 @@ import (
"io"
"net/http"
"reflect"
+ "runtime/debug"
"strings"
"sync/atomic"
"time"
@@ -24,7 +25,7 @@ func (rh *RouteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
tracker := timer.Start()
requestId := r.Header.Get(discover.HeaderRequestID)
if requestId == "" {
- requestId = MakeId(12)
+ requestId = IDMaker.Get10Bytes14MPerSecond()
r.Header.Set(discover.HeaderRequestID, requestId)
}
@@ -32,24 +33,104 @@ func (rh *RouteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
request.Id = requestId
response := NewResponse(w)
response.Id = requestId
- defer response.checkWriteHeader()
+ requestLogger := log.New(requestId)
+
+ // 0. 延迟处理日志与状态检查
+ var s *webServiceType
+ var authLevel int
+ var priority int
+ var args = make(map[string]any)
+
+ defer func() {
+ // 捕捉 Panic
+ if err := recover(); err != nil {
+ requestLogger.Error("panic recovered", "error", err, "stack", string(debug.Stack()))
+ if !response.changed {
+ response.WriteHeader(http.StatusInternalServerError)
+ outputResult(response, "internal server error")
+ }
+ }
+
+ response.checkWriteHeader()
+
+ // 记录日志
+ if (s == nil || !s.options.NoLog200 || response.Code != 200) &&
+ !(Config.NoLogGets && r.Method == http.MethodGet && response.Code == 200) {
+
+ scheme := "http"
+ if r.TLS != nil {
+ scheme = "https"
+ }
+ usedTime := float32(tracker.Stop().Seconds())
+
+ // 过滤请求头
+ reqHeaders := make(map[string]string)
+ noLogHeaders := strings.Split(Config.NoLogHeaders, ",")
+ for k, v := range r.Header {
+ skip := false
+ for _, nl := range noLogHeaders {
+ if nl != "" && strings.EqualFold(k, strings.TrimSpace(nl)) {
+ skip = true
+ break
+ }
+ }
+ if !skip {
+ reqHeaders[k] = strings.Join(v, ", ")
+ }
+ }
+
+ // 过滤响应头
+ respHeaders := make(map[string]string)
+ for k, v := range response.Header() {
+ respHeaders[k] = strings.Join(v, ", ")
+ }
+
+ // 处理响应内容截断
+ var respData any
+ if response.Code != 200 {
+ if len(response.body) < 1024 {
+ respData = string(response.body)
+ } else {
+ respData = string(response.body[:1024]) + "..."
+ }
+ } else if Config.NoLogOutputFields != "" {
+ // 简单的字段过滤逻辑 (如果是 JSON 对象)
+ // 这里可以根据 Config.NoLogOutputFields, LogOutputArrayNum, LogOutputFieldSize 进行更复杂的处理
+ // 暂按字符串截断处理
+ if len(response.body) > 0 {
+ respData = "[content hidden or truncated]"
+ }
+ }
+
+ logRequest(
+ requestLogger,
+ r.Method, r.URL.Path, hostOnly(r.Host), scheme, r.Proto,
+ request.ClientIp(), serverId, Config.App, "", // node 暂无
+ r.Header.Get(discover.HeaderFromApp), r.Header.Get(discover.HeaderFromNode),
+ "", request.DeviceId(), request.SessionId(), requestId,
+ request.Header.Get(discover.HeaderClientAppName), request.Header.Get(discover.HeaderClientAppVersion),
+ authLevel, priority,
+ reqHeaders, args,
+ response.Code, usedTime,
+ respHeaders, cast.String(respData), uint(len(response.body)),
+ )
+ }
+ }()
// 处理 SessionId 和 DeviceId
handleClientKeys(request, response)
- requestLogger := log.New(requestId)
-
- // 0. 处理重写 (Rewrite)
+ // 1. 处理重写 (Rewrite)
if processRewrite(request, response, requestLogger) {
return
}
- // 处理代理 (Proxy)
+ // 2. 处理代理 (Proxy)
if processProxy(request, response, requestLogger) {
return
}
- // 1. 路由匹配
+ // 3. 路由匹配
path := r.URL.Path
host := r.Host
@@ -58,13 +139,13 @@ func (rh *RouteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
- s, ws := findService(r.Method, host, path)
+ var ws *websocketServiceType
+ s, ws = findService(r.Method, host, path)
- // 2. 参数解析 (Form & Body)
- args := make(map[string]any)
+ // 4. 参数解析 (Form & Body)
parseRequestArgs(request, args)
- // 3. 前置过滤器
+ // 5. 前置过滤器
var result any
for _, filter := range inFilters {
result = filter(&args, request, response, requestLogger)
@@ -73,14 +154,12 @@ func (rh *RouteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
- authLevel := 0
- priority := 0
if s != nil {
authLevel = s.authLevel
priority = s.options.Priority
}
- // 4. 处理业务执行 (WS 或 Web)
+ // 6. 处理业务执行 (WS 或 Web)
if result == nil {
if ws != nil {
authLevel = ws.authLevel
@@ -101,11 +180,11 @@ func (rh *RouteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
- if s == nil && result == nil {
+ if s == nil && result == nil && !response.changed {
response.WriteHeader(http.StatusNotFound)
}
- // 5. 后置过滤器
+ // 7. 后置过滤器
for _, filter := range outFilters {
newResult, done := filter(args, request, response, result, requestLogger)
if newResult != nil {
@@ -116,50 +195,13 @@ func (rh *RouteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
- // 6. 输出结果
+ // 8. 输出结果
outputResult(response, result)
+}
- // 7. 记录日志
- if s == nil || !s.options.NoLog200 || response.Code != 200 {
- scheme := "http"
- if r.TLS != nil {
- scheme = "https"
- }
- usedTime := float32(tracker.Stop().Seconds())
-
- // 获取一些 Header 信息
- reqHeaders := make(map[string]string)
- for k, v := range r.Header {
- reqHeaders[k] = strings.Join(v, ", ")
- }
- respHeaders := make(map[string]string)
- for k, v := range response.Header() {
- respHeaders[k] = strings.Join(v, ", ")
- }
-
- // 限制记录的 Body 长度
- respData := ""
- if response.Code != 200 {
- if len(response.body) < 1024 {
- respData = string(response.body)
- } else {
- respData = string(response.body[:1024]) + "..."
- }
- }
-
- logRequest(
- requestLogger,
- r.Method, path, host, scheme, r.Proto,
- request.ClientIp(), serverId, "", "", // app, node 暂无
- r.Header.Get(discover.HeaderFromApp), r.Header.Get(discover.HeaderFromNode),
- "", request.DeviceId(), request.SessionId(), requestId,
- request.Header.Get(discover.HeaderClientAppName), request.Header.Get(discover.HeaderClientAppVersion),
- authLevel, priority,
- reqHeaders, args,
- response.Code, usedTime,
- respHeaders, respData, uint(len(response.body)),
- )
- }
+func hostOnly(host string) string {
+ h, _, _ := strings.Cut(host, ":")
+ return h
}
func findService(method, host, path string) (*webServiceType, *websocketServiceType) {
@@ -344,7 +386,7 @@ func handleClientKeys(request *Request, response *Response) {
if sessionIdMaker != nil {
sessionId = sessionIdMaker()
} else {
- sessionId = MakeId(14)
+ sessionId = IDMaker.Get11Bytes900MPerSecond()
}
if !Config.SessionWithoutCookie {
http.SetCookie(response.Writer, &http.Cookie{
@@ -368,7 +410,7 @@ func handleClientKeys(request *Request, response *Response) {
}
}
if deviceId == "" {
- deviceId = MakeId(14)
+ deviceId = IDMaker.Get11Bytes900MPerSecond()
if !Config.DeviceWithoutCookie {
http.SetCookie(response.Writer, &http.Cookie{
Name: usedDeviceIdKey,
diff --git a/handler_test.go b/handler_test.go
index 54da580..f2b33fd 100644
--- a/handler_test.go
+++ b/handler_test.go
@@ -65,3 +65,19 @@ func TestServeHTTP_VerifyFailed(t *testing.T) {
t.Errorf("Expected status 400, got %d", w.Code)
}
}
+
+func TestServeHTTP_Panic(t *testing.T) {
+ Host("*").GET("/panic", func() string {
+ panic("intentional panic")
+ })
+
+ rh := &RouteHandler{}
+ req := httptest.NewRequest("GET", "/panic", nil)
+ w := httptest.NewRecorder()
+
+ rh.ServeHTTP(w, req)
+
+ if w.Code != http.StatusInternalServerError {
+ t.Errorf("Expected status 500, got %d", w.Code)
+ }
+}
diff --git a/log.go b/log.go
index 3cdbc38..8501c8c 100644
--- a/log.go
+++ b/log.go
@@ -128,96 +128,6 @@ func logRequest(
logger.Log(entry)
}
-type TaskLog struct {
- log.BaseLog
- Task string `log:"pos:6"`
- UsedTime float32 `log:"pos:7"`
- Success bool `log:"pos:8"`
- Message string `log:"pos:9"`
-}
-
-func (l *TaskLog) Reset() {
- l.BaseLog.Reset()
- l.Task = ""
- l.UsedTime = 0
- l.Success = false
- l.Message = ""
-}
-
-type MonitorLog struct {
- log.BaseLog
- Target string `log:"pos:6"`
- Status int `log:"pos:7"`
- Message string `log:"pos:8"`
-}
-
-func (l *MonitorLog) Reset() {
- l.BaseLog.Reset()
- l.Target = ""
- l.Status = 0
- l.Message = ""
-}
-
-type StatisticLog struct {
- log.BaseLog
- Category string `log:"pos:6"`
- Item string `log:"pos:7"`
- Value float64 `log:"pos:8"`
-}
-
-func (l *StatisticLog) Reset() {
- l.BaseLog.Reset()
- l.Category = ""
- l.Item = ""
- l.Value = 0
-}
-
-func logTask(logger *log.Logger, taskName string, usedTime float32, success bool, message string, extra ...any) {
- if logger.CheckLevel(log.INFO) {
- entry := log.GetEntry[TaskLog]()
- logger.FillBase(entry.GetBaseLog(), log.LogTypeTask)
- entry.Task = taskName
- entry.UsedTime = usedTime
- entry.Success = success
- entry.Message = message
- if len(extra) > 0 {
- cast.FillMap(&entry.Extra, extra)
- }
- logger.Log(entry)
- }
-}
-
-func logMonitor(logger *log.Logger, target string, status int, message string, extra ...any) {
- if logger.CheckLevel(log.INFO) {
- entry := log.GetEntry[MonitorLog]()
- logger.FillBase(entry.GetBaseLog(), log.LogTypeMonitor)
- entry.Target = target
- entry.Status = status
- entry.Message = message
- if len(extra) > 0 {
- cast.FillMap(&entry.Extra, extra)
- }
- logger.Log(entry)
- }
-}
-
-func logStatistic(logger *log.Logger, category, item string, value float64, extra ...any) {
- if logger.CheckLevel(log.INFO) {
- entry := log.GetEntry[StatisticLog]()
- logger.FillBase(entry.GetBaseLog(), log.LogTypeStatistic)
- entry.Category = category
- entry.Item = item
- entry.Value = value
- if len(extra) > 0 {
- cast.FillMap(&entry.Extra, extra)
- }
- logger.Log(entry)
- }
-}
-
func init() {
log.RegisterType(log.LogTypeRequest, &RequestLog{})
- log.RegisterType(log.LogTypeTask, &TaskLog{})
- log.RegisterType(log.LogTypeMonitor, &MonitorLog{})
- log.RegisterType(log.LogTypeStatistic, &StatisticLog{})
}
diff --git a/server.go b/server.go
index 3bc0471..f259881 100644
--- a/server.go
+++ b/server.go
@@ -3,6 +3,7 @@ package service
import (
"apigo.cc/go/discover"
"apigo.cc/go/log"
+ "apigo.cc/go/redis"
"apigo.cc/go/safe"
"context"
"fmt"
@@ -70,14 +71,25 @@ func (as *AsyncServer) start() {
}
// 检查是否需要启动服务发现
- appName := Config.App
- if appName == "" {
- appName = GetDefaultName()
+ if Config.App == "" {
+ Config.App = GetDefaultName()
}
+ appName := Config.App
if appName != "" || Config.Register != "" {
as.useDiscover = true
}
+ // 初始化服务器唯一标识 (8位,物理上限 3,844/s)
+ serverId = IDMaker.Get8Bytes4KPerSecond()
+
+ // 初始化分布式 ID 生成器
+ if Config.IdServer != "" {
+ rd := redis.GetRedis(Config.IdServer, log.New(serverId))
+ if rd.Error == nil {
+ IDMaker = redis.NewIDMaker(rd)
+ }
+ }
+
listener, err := net.Listen("tcp", addr)
if err != nil {
log.DefaultLogger.Error("failed to listen", "addr", addr, "error", err.Error())
@@ -101,7 +113,12 @@ func (as *AsyncServer) start() {
}
as.server = &http.Server{
- Handler: handler,
+ Handler: handler,
+ ReadTimeout: time.Duration(Config.ReadTimeout) * time.Millisecond,
+ ReadHeaderTimeout: time.Duration(Config.ReadHeaderTimeout) * time.Millisecond,
+ WriteTimeout: time.Duration(Config.WriteTimeout) * time.Millisecond,
+ IdleTimeout: time.Duration(Config.IdleTimeout) * time.Millisecond,
+ MaxHeaderBytes: Config.MaxHeaderBytes,
}
// 启动服务发现
diff --git a/utility.go b/utility.go
index c64d971..dc46e98 100644
--- a/utility.go
+++ b/utility.go
@@ -2,34 +2,27 @@ package service
import (
"apigo.cc/go/id"
- "apigo.cc/go/log"
- "apigo.cc/go/redis"
"net"
"os"
"path"
"runtime/debug"
- "sync"
+ "strings"
)
-var (
- idMaker IDMakerInterface
- idMakerLock sync.Mutex
-)
+// IDMaker 全局 ID 生成器,默认指向单机版,启动后若配置了 IdServer 会被替换为 Redis 版
+var IDMaker = id.DefaultIDMaker
// GetDefaultName 获取默认应用名称
func GetDefaultName() string {
- name := os.Getenv("DISCOVER_APP")
- if name == "" {
- name = os.Getenv("discover_app")
- }
- if name == "" {
- if info, ok := debug.ReadBuildInfo(); ok && info.Path != "" && info.Path != "command-line-arguments" {
- name = path.Base(info.Path)
- }
+ name := ""
+ if info, ok := debug.ReadBuildInfo(); ok && info.Path != "" && info.Path != "command-line-arguments" {
+ name = path.Base(info.Path)
}
if name == "" {
name = path.Base(os.Args[0])
}
+ // 处理 Windows 下的 .exe 后缀
+ name = strings.TrimSuffix(name, ".exe")
return name
}
@@ -54,51 +47,3 @@ func GetServerIp() string {
}
return "127.0.0.1"
}
-
-// IDMakerInterface ID 生成器接口
-type IDMakerInterface interface {
- Get(size int) string
- GetForMysql(size int) string
- GetForPostgreSQL(size int) string
-}
-
-func getIDMaker() IDMakerInterface {
- if idMaker != nil {
- return idMaker
- }
-
- idMakerLock.Lock()
- defer idMakerLock.Unlock()
-
- if idMaker != nil {
- return idMaker
- }
-
- if Config.IdServer != "" {
- rd := redis.GetRedis(Config.IdServer, log.DefaultLogger)
- if rd.Error == nil {
- idMaker = redis.NewIDMaker(rd)
- }
- }
-
- if idMaker == nil {
- idMaker = id.DefaultIDMaker
- }
-
- return idMaker
-}
-
-// MakeId 生成指定长度的 ID
-func MakeId(size int) string {
- return getIDMaker().Get(size)
-}
-
-// MakeIdForMysql 生成适用于 MySQL 的有序 ID
-func MakeIdForMysql(size int) string {
- return getIDMaker().GetForMysql(size)
-}
-
-// MakeIdForPostgreSQL 生成适用于 PostgreSQL 的有序 ID
-func MakeIdForPostgreSQL(size int) string {
- return getIDMaker().GetForPostgreSQL(size)
-}