refactor: 后端日志系统重构
- 新增模块化日志器(pkg/logger/module.go) - 新增 GORM 日志适配器 - 统一日志入口,移除所有 zap.L() 全局 logger 调用 - 字段标准化 - 启动阶段使用结构化日志 - 更新所有相关测试
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
appErrors "nex/backend/pkg/errors"
|
||||
@@ -33,12 +34,12 @@ type ServerConfig struct {
|
||||
// DatabaseConfig 数据库配置
|
||||
type DatabaseConfig struct {
|
||||
Driver string `yaml:"driver" mapstructure:"driver" validate:"required,oneof=sqlite mysql"`
|
||||
Path string `yaml:"path" mapstructure:"path" validate:"required_if=driver sqlite"`
|
||||
Host string `yaml:"host" mapstructure:"host" validate:"required_if=driver mysql"`
|
||||
Port int `yaml:"port" mapstructure:"port" validate:"required_if=driver mysql,min=1,max=65535"`
|
||||
User string `yaml:"user" mapstructure:"user" validate:"required_if=driver mysql"`
|
||||
Path string `yaml:"path" mapstructure:"path" validate:"required_if=Driver sqlite"`
|
||||
Host string `yaml:"host" mapstructure:"host" validate:"required_if=Driver mysql"`
|
||||
Port int `yaml:"port" mapstructure:"port" validate:"required_if=Driver mysql,min=1,max=65535"`
|
||||
User string `yaml:"user" mapstructure:"user" validate:"required_if=Driver mysql"`
|
||||
Password string `yaml:"password" mapstructure:"password"`
|
||||
DBName string `yaml:"dbname" mapstructure:"dbname" validate:"required_if=driver mysql"`
|
||||
DBName string `yaml:"dbname" mapstructure:"dbname" validate:"required_if=Driver mysql"`
|
||||
MaxIdleConns int `yaml:"max_idle_conns" mapstructure:"max_idle_conns" validate:"required,min=1"`
|
||||
MaxOpenConns int `yaml:"max_open_conns" mapstructure:"max_open_conns" validate:"required,min=1"`
|
||||
ConnMaxLifetime time.Duration `yaml:"conn_max_lifetime" mapstructure:"conn_max_lifetime" validate:"required"`
|
||||
@@ -311,22 +312,24 @@ func (c *Config) Validate() error {
|
||||
}
|
||||
|
||||
// PrintSummary 打印配置摘要
|
||||
func (c *Config) PrintSummary() {
|
||||
fmt.Println("\nAI Gateway 启动配置")
|
||||
fmt.Println("==================")
|
||||
fmt.Printf("服务器端口: %d\n", c.Server.Port)
|
||||
func (c *Config) PrintSummary(logger *zap.Logger) {
|
||||
logger.Info("AI Gateway 启动配置",
|
||||
zap.Int("server_port", c.Server.Port),
|
||||
zap.String("database_driver", c.Database.Driver),
|
||||
zap.String("log_level", c.Log.Level),
|
||||
)
|
||||
|
||||
if c.Database.Driver == "mysql" {
|
||||
fmt.Printf("数据库类型: mysql\n")
|
||||
fmt.Printf("数据库地址: %s:%d/%s\n", c.Database.Host, c.Database.Port, c.Database.DBName)
|
||||
logger.Info("数据库配置",
|
||||
zap.String("driver", "mysql"),
|
||||
zap.String("host", c.Database.Host),
|
||||
zap.Int("port", c.Database.Port),
|
||||
zap.String("database", c.Database.DBName),
|
||||
)
|
||||
} else {
|
||||
fmt.Printf("数据库类型: sqlite\n")
|
||||
fmt.Printf("数据库路径: %s\n", c.Database.Path)
|
||||
logger.Info("数据库配置",
|
||||
zap.String("driver", "sqlite"),
|
||||
zap.String("path", c.Database.Path),
|
||||
)
|
||||
}
|
||||
fmt.Printf("日志级别: %s\n", c.Log.Level)
|
||||
fmt.Println("\n配置来源:")
|
||||
configPath, _ := GetConfigPath()
|
||||
fmt.Printf(" 配置文件: %s\n", configPath)
|
||||
fmt.Println(" 环境变量: 待统计")
|
||||
fmt.Println(" CLI 参数: 待统计")
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -171,7 +172,9 @@ func TestConfig_Validate(t *testing.T) {
|
||||
err := cfg.Validate()
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.errMsg)
|
||||
if err != nil {
|
||||
assert.Contains(t, err.Error(), tt.errMsg)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -302,7 +305,7 @@ func TestPrintSummary(t *testing.T) {
|
||||
t.Run("SQLite模式摘要", func(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
assert.NotPanics(t, func() {
|
||||
cfg.PrintSummary()
|
||||
cfg.PrintSummary(zap.NewNop())
|
||||
})
|
||||
})
|
||||
t.Run("MySQL模式摘要", func(t *testing.T) {
|
||||
@@ -313,7 +316,7 @@ func TestPrintSummary(t *testing.T) {
|
||||
cfg.Database.User = "nex"
|
||||
cfg.Database.DBName = "nex"
|
||||
assert.NotPanics(t, func() {
|
||||
cfg.PrintSummary()
|
||||
cfg.PrintSummary(zap.NewNop())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
|
||||
pkglogger "nex/backend/pkg/logger"
|
||||
)
|
||||
|
||||
// HTTPRequestSpec HTTP 请求规格
|
||||
@@ -33,13 +35,10 @@ type ConversionEngine struct {
|
||||
|
||||
// NewConversionEngine 创建转换引擎
|
||||
func NewConversionEngine(registry AdapterRegistry, logger *zap.Logger) *ConversionEngine {
|
||||
if logger == nil {
|
||||
logger = zap.L()
|
||||
}
|
||||
return &ConversionEngine{
|
||||
registry: registry,
|
||||
middlewareChain: NewMiddlewareChain(),
|
||||
logger: logger,
|
||||
logger: pkglogger.WithModule(logger, "conversion.engine"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +89,7 @@ func (e *ConversionEngine) ConvertHttpRequest(spec HTTPRequestSpec, clientProtoc
|
||||
rewrittenBody, err = providerAdapter.RewriteRequestModelName(spec.Body, provider.ModelName, interfaceType)
|
||||
if err != nil {
|
||||
e.logger.Warn("Smart Passthrough 改写请求失败,使用原始请求体",
|
||||
zap.String("error", err.Error()),
|
||||
zap.Error(err),
|
||||
zap.String("interface", string(interfaceType)))
|
||||
rewrittenBody = spec.Body
|
||||
}
|
||||
@@ -142,9 +141,9 @@ func (e *ConversionEngine) ConvertHttpResponse(spec HTTPResponseSpec, clientProt
|
||||
|
||||
rewrittenBody, err := adapter.RewriteResponseModelName(spec.Body, modelOverride, interfaceType)
|
||||
if err != nil {
|
||||
e.logger.Warn("Smart Passthrough 改写响应失败,使用原始响应体",
|
||||
zap.String("error", err.Error()),
|
||||
zap.String("interface", string(interfaceType)))
|
||||
e.logger.Warn("Smart Passthrough 改写响应失败,使用原始响应体",
|
||||
zap.Error(err),
|
||||
zap.String("interface", string(interfaceType)))
|
||||
return &spec, nil
|
||||
}
|
||||
|
||||
@@ -312,7 +311,7 @@ func (e *ConversionEngine) convertModelsResponseBody(clientAdapter, providerAdap
|
||||
}
|
||||
encoded, err := clientAdapter.EncodeModelsResponse(models)
|
||||
if err != nil {
|
||||
e.logger.Warn("编码 Models 响应失败,返回原始响应", zap.String("error", err.Error()))
|
||||
e.logger.Warn("编码 Models 响应失败,返回原始响应", zap.Error(err))
|
||||
return body, nil
|
||||
}
|
||||
return encoded, nil
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestConversionError_WithProviderProtocol(t *testing.T) {
|
||||
@@ -39,7 +40,7 @@ func TestConversionError_FullBuilder(t *testing.T) {
|
||||
|
||||
func TestEngine_Use(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
called := false
|
||||
engine.Use(&testMiddleware{fn: func(req *canonical.CanonicalRequest, cp, pp string, ctx *ConversionContext) (*canonical.CanonicalRequest, error) {
|
||||
called = true
|
||||
@@ -66,7 +67,7 @@ func TestEngine_Use(t *testing.T) {
|
||||
|
||||
func TestConvertHttpRequest_DecodeError(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
clientAdapter := newMockAdapter("client", false)
|
||||
clientAdapter.decodeReqFn = func(raw []byte) (*canonical.CanonicalRequest, error) {
|
||||
return nil, errors.New("decode failed")
|
||||
@@ -82,7 +83,7 @@ func TestConvertHttpRequest_DecodeError(t *testing.T) {
|
||||
|
||||
func TestConvertHttpRequest_EncodeError(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
_ = engine.RegisterAdapter(newMockAdapter("client", false))
|
||||
providerAdapter := newMockAdapter("provider", false)
|
||||
providerAdapter.encodeReqFn = func(req *canonical.CanonicalRequest, p *TargetProvider) ([]byte, error) {
|
||||
@@ -98,7 +99,7 @@ func TestConvertHttpRequest_EncodeError(t *testing.T) {
|
||||
|
||||
func TestConvertHttpResponse_CrossProtocol(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
clientAdapter := newMockAdapter("client", false)
|
||||
clientAdapter.encodeRespFn = func(resp *canonical.CanonicalResponse) ([]byte, error) {
|
||||
@@ -121,7 +122,7 @@ func TestConvertHttpResponse_CrossProtocol(t *testing.T) {
|
||||
|
||||
func TestConvertHttpResponse_DecodeError(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
providerAdapter := newMockAdapter("provider", false)
|
||||
providerAdapter.decodeRespFn = func(raw []byte) (*canonical.CanonicalResponse, error) {
|
||||
return nil, errors.New("decode error")
|
||||
@@ -135,7 +136,7 @@ func TestConvertHttpResponse_DecodeError(t *testing.T) {
|
||||
|
||||
func TestConvertHttpRequest_EmbeddingInterface(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
clientAdapter := newMockAdapter("client", false)
|
||||
clientAdapter.ifaceType = InterfaceTypeEmbeddings
|
||||
@@ -158,7 +159,7 @@ func TestConvertHttpRequest_EmbeddingInterface(t *testing.T) {
|
||||
|
||||
func TestConvertHttpRequest_RerankInterface(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
clientAdapter := newMockAdapter("client", false)
|
||||
clientAdapter.ifaceType = InterfaceTypeRerank
|
||||
@@ -178,7 +179,7 @@ func TestConvertHttpRequest_RerankInterface(t *testing.T) {
|
||||
|
||||
func TestConvertHttpResponse_EmbeddingInterface(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
clientAdapter := newMockAdapter("client", false)
|
||||
clientAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeEmbeddings: true}
|
||||
@@ -196,7 +197,7 @@ func TestConvertHttpResponse_EmbeddingInterface(t *testing.T) {
|
||||
|
||||
func TestConvertHttpResponse_RerankInterface(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
clientAdapter := newMockAdapter("client", false)
|
||||
clientAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeRerank: true}
|
||||
@@ -214,7 +215,7 @@ func TestConvertHttpResponse_RerankInterface(t *testing.T) {
|
||||
|
||||
func TestConvertHttpRequest_ModelsInterface_Passthrough(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
clientAdapter := newMockAdapter("client", false)
|
||||
clientAdapter.ifaceType = InterfaceTypeModels
|
||||
providerAdapter := newMockAdapter("provider", false)
|
||||
@@ -232,7 +233,7 @@ func TestConvertHttpRequest_ModelsInterface_Passthrough(t *testing.T) {
|
||||
|
||||
func TestConvertHttpResponse_ModelsInterface(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
clientAdapter := newMockAdapter("client", false)
|
||||
clientAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeModels: true}
|
||||
providerAdapter := newMockAdapter("provider", false)
|
||||
@@ -249,7 +250,7 @@ func TestConvertHttpResponse_ModelsInterface(t *testing.T) {
|
||||
|
||||
func TestConvertHttpResponse_ModelInfoInterface(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
clientAdapter := newMockAdapter("client", false)
|
||||
clientAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeModelInfo: true}
|
||||
providerAdapter := newMockAdapter("provider", false)
|
||||
@@ -324,7 +325,7 @@ var _ = json.Marshal
|
||||
|
||||
func TestConvertEmbeddingBody_DecodeError(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
clientAdapter := newMockAdapter("client", false)
|
||||
clientAdapter.decodeEmbeddingReqFn = func(raw []byte) (*canonical.CanonicalEmbeddingRequest, error) {
|
||||
@@ -344,7 +345,7 @@ func TestConvertEmbeddingBody_DecodeError(t *testing.T) {
|
||||
|
||||
func TestConvertRerankBody_DecodeError(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
clientAdapter := newMockAdapter("client", false)
|
||||
clientAdapter.decodeRerankReqFn = func(raw []byte) (*canonical.CanonicalRerankRequest, error) {
|
||||
@@ -364,7 +365,7 @@ func TestConvertRerankBody_DecodeError(t *testing.T) {
|
||||
|
||||
func TestConvertBody_UnknownInterfaceType(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
clientAdapter := newMockAdapter("client", false)
|
||||
providerAdapter := newMockAdapter("provider", false)
|
||||
|
||||
@@ -203,7 +203,7 @@ func (e *noopStreamEncoder) Flush() [][]byte
|
||||
|
||||
func TestNewConversionEngine(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
assert.NotNil(t, engine)
|
||||
assert.Equal(t, registry, engine.GetRegistry())
|
||||
}
|
||||
@@ -211,7 +211,7 @@ func TestNewConversionEngine(t *testing.T) {
|
||||
func TestNewConversionEngine_LoggerInjection(t *testing.T) {
|
||||
t.Run("nil_logger_uses_global", func(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
assert.NotNil(t, engine.logger)
|
||||
})
|
||||
|
||||
@@ -219,13 +219,14 @@ func TestNewConversionEngine_LoggerInjection(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
customLogger := zap.NewNop()
|
||||
engine := NewConversionEngine(registry, customLogger)
|
||||
assert.Equal(t, customLogger, engine.logger)
|
||||
assert.NotNil(t, engine.logger)
|
||||
assert.Contains(t, engine.logger.Name(), "conversion.engine")
|
||||
})
|
||||
}
|
||||
|
||||
func TestRegisterAdapter(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
adapter := newMockAdapter("test-proto", true)
|
||||
err := engine.RegisterAdapter(adapter)
|
||||
@@ -237,7 +238,7 @@ func TestRegisterAdapter(t *testing.T) {
|
||||
|
||||
func TestIsPassthrough_SameProtocol(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
adapter := newMockAdapter("openai", true)
|
||||
_ = engine.RegisterAdapter(adapter)
|
||||
|
||||
@@ -246,7 +247,7 @@ func TestIsPassthrough_SameProtocol(t *testing.T) {
|
||||
|
||||
func TestIsPassthrough_DifferentProtocol(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
_ = engine.RegisterAdapter(newMockAdapter("openai", true))
|
||||
_ = engine.RegisterAdapter(newMockAdapter("anthropic", true))
|
||||
|
||||
@@ -255,7 +256,7 @@ func TestIsPassthrough_DifferentProtocol(t *testing.T) {
|
||||
|
||||
func TestIsPassthrough_NoPassthrough(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
_ = engine.RegisterAdapter(newMockAdapter("custom", false))
|
||||
|
||||
assert.False(t, engine.IsPassthrough("custom", "custom"))
|
||||
@@ -263,7 +264,7 @@ func TestIsPassthrough_NoPassthrough(t *testing.T) {
|
||||
|
||||
func TestDetectInterfaceType(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
adapter := newMockAdapter("test", true)
|
||||
adapter.ifaceType = InterfaceTypeChat
|
||||
_ = engine.RegisterAdapter(adapter)
|
||||
@@ -275,7 +276,7 @@ func TestDetectInterfaceType(t *testing.T) {
|
||||
|
||||
func TestDetectInterfaceType_NonExistentProtocol(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
_, err := engine.DetectInterfaceType("/v1/chat", "nonexistent")
|
||||
assert.Error(t, err)
|
||||
@@ -283,7 +284,7 @@ func TestDetectInterfaceType_NonExistentProtocol(t *testing.T) {
|
||||
|
||||
func TestConvertHttpRequest_Passthrough(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
_ = engine.RegisterAdapter(newMockAdapter("openai", true))
|
||||
|
||||
provider := NewTargetProvider("https://api.openai.com/v1", "sk-test", "gpt-4")
|
||||
@@ -301,7 +302,7 @@ func TestConvertHttpRequest_Passthrough(t *testing.T) {
|
||||
|
||||
func TestConvertHttpRequest_CrossProtocol(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
clientAdapter := newMockAdapter("client-proto", false)
|
||||
clientAdapter.decodeReqFn = func(raw []byte) (*canonical.CanonicalRequest, error) {
|
||||
@@ -333,7 +334,7 @@ func TestConvertHttpRequest_CrossProtocol(t *testing.T) {
|
||||
|
||||
func TestConvertHttpResponse_Passthrough(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
_ = engine.RegisterAdapter(newMockAdapter("openai", true))
|
||||
|
||||
spec := HTTPResponseSpec{
|
||||
@@ -349,7 +350,7 @@ func TestConvertHttpResponse_Passthrough(t *testing.T) {
|
||||
|
||||
func TestCreateStreamConverter_Passthrough(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
_ = engine.RegisterAdapter(newMockAdapter("openai", true))
|
||||
|
||||
converter, err := engine.CreateStreamConverter("openai", "openai", "", InterfaceTypeChat)
|
||||
@@ -360,7 +361,7 @@ func TestCreateStreamConverter_Passthrough(t *testing.T) {
|
||||
|
||||
func TestCreateStreamConverter_Canonical(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
_ = engine.RegisterAdapter(newMockAdapter("client", false))
|
||||
_ = engine.RegisterAdapter(newMockAdapter("provider", false))
|
||||
|
||||
@@ -372,7 +373,7 @@ func TestCreateStreamConverter_Canonical(t *testing.T) {
|
||||
|
||||
func TestEncodeError(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
_ = engine.RegisterAdapter(newMockAdapter("openai", true))
|
||||
|
||||
convErr := NewConversionError(ErrorCodeInvalidInput, "测试错误")
|
||||
@@ -384,7 +385,7 @@ func TestEncodeError(t *testing.T) {
|
||||
|
||||
func TestEncodeError_NonExistentProtocol(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
convErr := NewConversionError(ErrorCodeInvalidInput, "测试错误")
|
||||
body, statusCode, err := engine.EncodeError(convErr, "nonexistent")
|
||||
@@ -417,7 +418,7 @@ func TestRegistry_GetNonExistent(t *testing.T) {
|
||||
|
||||
func TestConvertHttpResponse_ModelOverride_CrossProtocol(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
clientAdapter := newMockAdapter("client", false)
|
||||
clientAdapter.encodeRespFn = func(resp *canonical.CanonicalResponse) ([]byte, error) {
|
||||
@@ -446,7 +447,7 @@ func TestConvertHttpResponse_ModelOverride_CrossProtocol(t *testing.T) {
|
||||
|
||||
func TestConvertHttpResponse_ModelOverride_SameProtocol(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
// 使用真实 OpenAI adapter 验证 Smart Passthrough 改写
|
||||
openaiAdapter := newMockAdapter("openai", true)
|
||||
@@ -476,7 +477,7 @@ func TestConvertHttpResponse_ModelOverride_SameProtocol(t *testing.T) {
|
||||
|
||||
func TestCreateStreamConverter_ModelOverride_SmartPassthrough(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
openaiAdapter := newMockAdapter("openai", true)
|
||||
openaiAdapter.rewriteRespFn = func(body []byte, newModel string, ifaceType InterfaceType) ([]byte, error) {
|
||||
@@ -506,7 +507,7 @@ func TestCreateStreamConverter_ModelOverride_SmartPassthrough(t *testing.T) {
|
||||
|
||||
func TestCreateStreamConverter_ModelOverride_CrossProtocol(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
// provider adapter 解码出含 model 的流式事件
|
||||
providerAdapter := newMockAdapter("provider", false)
|
||||
@@ -560,7 +561,7 @@ func TestCreateStreamConverter_ModelOverride_CrossProtocol(t *testing.T) {
|
||||
|
||||
func TestCreateStreamConverter_ModelOverride_CrossProtocol_Empty(t *testing.T) {
|
||||
registry := NewMemoryRegistry()
|
||||
engine := NewConversionEngine(registry, nil)
|
||||
engine := NewConversionEngine(registry, zap.NewNop())
|
||||
|
||||
providerAdapter := newMockAdapter("provider", false)
|
||||
providerAdapter.streamDecoderFn = func() StreamDecoder {
|
||||
|
||||
@@ -11,22 +11,24 @@ import (
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
|
||||
"nex/backend/internal/config"
|
||||
pkglogger "nex/backend/pkg/logger"
|
||||
)
|
||||
|
||||
func Init(cfg *config.DatabaseConfig, zapLogger *zap.Logger) (*gorm.DB, error) {
|
||||
db, err := initDB(cfg, zapLogger)
|
||||
moduleLogger := pkglogger.WithModule(zapLogger, "database")
|
||||
|
||||
db, err := initDB(cfg, moduleLogger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("初始化数据库失败: %w", err)
|
||||
}
|
||||
|
||||
if err := runMigrations(db, cfg.Driver, zapLogger); err != nil {
|
||||
if err := runMigrations(db, cfg.Driver, moduleLogger); err != nil {
|
||||
return nil, fmt.Errorf("数据库迁移失败: %w", err)
|
||||
}
|
||||
|
||||
configurePool(db, cfg, zapLogger)
|
||||
configurePool(db, cfg, moduleLogger)
|
||||
|
||||
return db, nil
|
||||
}
|
||||
@@ -40,8 +42,10 @@ func Close(db *gorm.DB) {
|
||||
}
|
||||
|
||||
func initDB(cfg *config.DatabaseConfig, zapLogger *zap.Logger) (*gorm.DB, error) {
|
||||
gormLogger := pkglogger.NewGormLogger(zapLogger)
|
||||
|
||||
gormConfig := &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Info),
|
||||
Logger: gormLogger,
|
||||
}
|
||||
|
||||
switch cfg.Driver {
|
||||
|
||||
@@ -5,9 +5,10 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
pkglogger "nex/backend/pkg/logger"
|
||||
)
|
||||
|
||||
// Logging 日志中间件
|
||||
func Logging(logger *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
@@ -15,12 +16,17 @@ func Logging(logger *zap.Logger) gin.HandlerFunc {
|
||||
query := c.Request.URL.RawQuery
|
||||
|
||||
requestID, _ := c.Get(RequestIDKey)
|
||||
var requestIDStr string
|
||||
if id, ok := requestID.(string); ok {
|
||||
requestIDStr = id
|
||||
}
|
||||
|
||||
logger.Info("请求开始",
|
||||
zap.String("method", c.Request.Method),
|
||||
zap.String("path", path),
|
||||
zap.String("query", query),
|
||||
zap.String("client_ip", c.ClientIP()),
|
||||
zap.Any("request_id", requestID),
|
||||
pkglogger.Method(c.Request.Method),
|
||||
pkglogger.Path(path),
|
||||
pkglogger.Query(query),
|
||||
pkglogger.ClientIP(c.ClientIP()),
|
||||
pkglogger.RequestID(requestIDStr),
|
||||
)
|
||||
|
||||
c.Next()
|
||||
@@ -29,12 +35,12 @@ func Logging(logger *zap.Logger) gin.HandlerFunc {
|
||||
statusCode := c.Writer.Status()
|
||||
|
||||
logger.Info("请求结束",
|
||||
zap.Int("status", statusCode),
|
||||
zap.String("method", c.Request.Method),
|
||||
zap.String("path", path),
|
||||
zap.Duration("latency", latency),
|
||||
zap.Int("body_size", c.Writer.Size()),
|
||||
zap.Any("request_id", requestID),
|
||||
pkglogger.StatusCode(statusCode),
|
||||
pkglogger.Method(c.Request.Method),
|
||||
pkglogger.Path(path),
|
||||
pkglogger.Latency(latency),
|
||||
pkglogger.BodySize(c.Writer.Size()),
|
||||
pkglogger.RequestID(requestIDStr),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"nex/backend/internal/provider"
|
||||
"nex/backend/internal/service"
|
||||
"nex/backend/pkg/modelid"
|
||||
pkglogger "nex/backend/pkg/logger"
|
||||
)
|
||||
|
||||
// ProxyHandler 统一代理处理器
|
||||
@@ -29,14 +30,14 @@ type ProxyHandler struct {
|
||||
}
|
||||
|
||||
// NewProxyHandler 创建统一代理处理器
|
||||
func NewProxyHandler(engine *conversion.ConversionEngine, client provider.ProviderClient, routingService service.RoutingService, providerService service.ProviderService, statsService service.StatsService) *ProxyHandler {
|
||||
func NewProxyHandler(engine *conversion.ConversionEngine, client provider.ProviderClient, routingService service.RoutingService, providerService service.ProviderService, statsService service.StatsService, logger *zap.Logger) *ProxyHandler {
|
||||
return &ProxyHandler{
|
||||
engine: engine,
|
||||
client: client,
|
||||
routingService: routingService,
|
||||
providerService: providerService,
|
||||
statsService: statsService,
|
||||
logger: zap.L(),
|
||||
logger: pkglogger.WithModule(logger, "handler.proxy"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +332,7 @@ func (h *ProxyHandler) handleModelInfo(c *gin.Context, unifiedID string, adapter
|
||||
// 使用 adapter 编码返回
|
||||
body, err := adapter.EncodeModelInfoResponse(modelInfo)
|
||||
if err != nil {
|
||||
h.logger.Error("编码 ModelInfo 响应失败", zap.String("error", err.Error()))
|
||||
h.logger.Error("编码 ModelInfo 响应失败", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "编码响应失败"})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"nex/backend/internal/conversion"
|
||||
"nex/backend/internal/conversion/anthropic"
|
||||
@@ -31,7 +32,7 @@ func init() {
|
||||
func setupProxyEngine(t *testing.T) *conversion.ConversionEngine {
|
||||
t.Helper()
|
||||
registry := conversion.NewMemoryRegistry()
|
||||
engine := conversion.NewConversionEngine(registry, nil)
|
||||
engine := conversion.NewConversionEngine(registry, zap.NewNop())
|
||||
require.NoError(t, registry.Register(openai.NewAdapter()))
|
||||
require.NoError(t, registry.Register(anthropic.NewAdapter()))
|
||||
return engine
|
||||
@@ -44,6 +45,7 @@ func newTestProxyHandler(engine *conversion.ConversionEngine, client *mocks.Mock
|
||||
routingSvc,
|
||||
providerSvc,
|
||||
statsSvc,
|
||||
zap.NewNop(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -499,7 +501,7 @@ func TestProxyHandler_HandleStream_CreateStreamConverterError(t *testing.T) {
|
||||
defer ctrl.Finish()
|
||||
|
||||
registry := conversion.NewMemoryRegistry()
|
||||
engine := conversion.NewConversionEngine(registry, nil)
|
||||
engine := conversion.NewConversionEngine(registry, zap.NewNop())
|
||||
err := registry.Register(openai.NewAdapter())
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -527,7 +529,7 @@ func TestProxyHandler_HandleStream_ConvertRequestError(t *testing.T) {
|
||||
defer ctrl.Finish()
|
||||
|
||||
registry := conversion.NewMemoryRegistry()
|
||||
engine := conversion.NewConversionEngine(registry, nil)
|
||||
engine := conversion.NewConversionEngine(registry, zap.NewNop())
|
||||
require.NoError(t, registry.Register(openai.NewAdapter()))
|
||||
|
||||
routingSvc := mocks.NewMockRoutingService(ctrl)
|
||||
@@ -554,7 +556,7 @@ func TestProxyHandler_HandleNonStream_ConvertResponseError(t *testing.T) {
|
||||
defer ctrl.Finish()
|
||||
|
||||
registry := conversion.NewMemoryRegistry()
|
||||
engine := conversion.NewConversionEngine(registry, nil)
|
||||
engine := conversion.NewConversionEngine(registry, zap.NewNop())
|
||||
require.NoError(t, registry.Register(openai.NewAdapter()))
|
||||
require.NoError(t, registry.Register(anthropic.NewAdapter()))
|
||||
|
||||
@@ -623,7 +625,7 @@ func TestProxyHandler_ForwardPassthrough_CrossProtocol(t *testing.T) {
|
||||
defer ctrl.Finish()
|
||||
|
||||
registry := conversion.NewMemoryRegistry()
|
||||
engine := conversion.NewConversionEngine(registry, nil)
|
||||
engine := conversion.NewConversionEngine(registry, zap.NewNop())
|
||||
require.NoError(t, registry.Register(openai.NewAdapter()))
|
||||
|
||||
anthropicAdapter := anthropic.NewAdapter()
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"nex/backend/internal/conversion"
|
||||
pkgErrors "nex/backend/pkg/errors"
|
||||
pkglogger "nex/backend/pkg/logger"
|
||||
)
|
||||
|
||||
// StreamConfig 流式处理配置
|
||||
@@ -57,12 +58,12 @@ type ProviderClient interface {
|
||||
}
|
||||
|
||||
// NewClient 创建供应商客户端
|
||||
func NewClient() *Client {
|
||||
func NewClient(logger *zap.Logger) *Client {
|
||||
return &Client{
|
||||
httpClient: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
logger: zap.L(),
|
||||
logger: pkglogger.WithModule(logger, "provider.client"),
|
||||
streamCfg: DefaultStreamConfig(),
|
||||
}
|
||||
}
|
||||
@@ -186,7 +187,7 @@ func (c *Client) readStream(ctx context.Context, cancel context.CancelFunc, body
|
||||
c.logger.Error("流网络错误", zap.String("error", err.Error()))
|
||||
eventChan <- StreamEvent{Error: fmt.Errorf("网络错误: %w", err)}
|
||||
} else {
|
||||
c.logger.Error("流读取错误", zap.String("error", err.Error()))
|
||||
c.logger.Error("流读取错误", zap.Error(err))
|
||||
eventChan <- StreamEvent{Error: fmt.Errorf("读取错误: %w", err)}
|
||||
}
|
||||
return
|
||||
|
||||
@@ -13,12 +13,13 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"nex/backend/internal/conversion"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
client := NewClient()
|
||||
client := NewClient(zap.NewNop())
|
||||
require.NotNil(t, client)
|
||||
assert.NotNil(t, client.httpClient)
|
||||
assert.Equal(t, 4096, client.streamCfg.InitialBufferSize)
|
||||
@@ -44,7 +45,7 @@ func TestClient_Send_Success(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient()
|
||||
client := NewClient(zap.NewNop())
|
||||
spec := conversion.HTTPRequestSpec{
|
||||
URL: server.URL + "/v1/chat/completions",
|
||||
Method: "POST",
|
||||
@@ -68,7 +69,7 @@ func TestClient_Send_ErrorResponse(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient()
|
||||
client := NewClient(zap.NewNop())
|
||||
spec := conversion.HTTPRequestSpec{
|
||||
URL: server.URL + "/v1/chat/completions",
|
||||
Method: "POST",
|
||||
@@ -82,7 +83,7 @@ func TestClient_Send_ErrorResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClient_Send_ConnectionError(t *testing.T) {
|
||||
client := NewClient()
|
||||
client := NewClient(zap.NewNop())
|
||||
spec := conversion.HTTPRequestSpec{
|
||||
URL: "http://localhost:1/v1/chat/completions",
|
||||
Method: "POST",
|
||||
@@ -99,7 +100,7 @@ func TestClient_SendStream_CreatesChannel(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient()
|
||||
client := NewClient(zap.NewNop())
|
||||
spec := conversion.HTTPRequestSpec{
|
||||
URL: server.URL + "/v1/chat/completions",
|
||||
Method: "POST",
|
||||
@@ -121,7 +122,7 @@ func TestClient_SendStream_ErrorResponse(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient()
|
||||
client := NewClient(zap.NewNop())
|
||||
spec := conversion.HTTPRequestSpec{
|
||||
URL: server.URL + "/v1/chat/completions",
|
||||
Method: "POST",
|
||||
@@ -150,7 +151,7 @@ func TestClient_SendStream_SSEEvents(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient()
|
||||
client := NewClient(zap.NewNop())
|
||||
spec := conversion.HTTPRequestSpec{
|
||||
URL: server.URL + "/v1/chat/completions",
|
||||
Method: "POST",
|
||||
@@ -188,7 +189,7 @@ func TestClient_SendStream_ContextCancellation(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
client := NewClient()
|
||||
client := NewClient(zap.NewNop())
|
||||
spec := conversion.HTTPRequestSpec{
|
||||
URL: server.URL + "/v1/chat/completions",
|
||||
Method: "POST",
|
||||
@@ -218,7 +219,7 @@ func TestClient_Send_EmptyBody(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient()
|
||||
client := NewClient(zap.NewNop())
|
||||
spec := conversion.HTTPRequestSpec{
|
||||
URL: server.URL + "/v1/models",
|
||||
Method: "GET",
|
||||
@@ -246,7 +247,7 @@ func TestClient_SendStream_SlowSSE(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient()
|
||||
client := NewClient(zap.NewNop())
|
||||
spec := conversion.HTTPRequestSpec{
|
||||
URL: server.URL + "/v1/chat/completions",
|
||||
Method: "POST",
|
||||
@@ -287,7 +288,7 @@ func TestClient_SendStream_SplitSSEEvents(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient()
|
||||
client := NewClient(zap.NewNop())
|
||||
spec := conversion.HTTPRequestSpec{
|
||||
URL: server.URL + "/v1/chat/completions",
|
||||
Method: "POST",
|
||||
@@ -375,7 +376,7 @@ func TestClient_SendStream_MidStreamNetworkError(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient()
|
||||
client := NewClient(zap.NewNop())
|
||||
spec := conversion.HTTPRequestSpec{
|
||||
URL: server.URL + "/v1/chat/completions",
|
||||
Method: "POST",
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"nex/backend/internal/domain"
|
||||
"nex/backend/internal/repository"
|
||||
pkglogger "nex/backend/pkg/logger"
|
||||
)
|
||||
|
||||
type RoutingCache struct {
|
||||
@@ -27,7 +28,7 @@ func NewRoutingCache(
|
||||
return &RoutingCache{
|
||||
modelRepo: modelRepo,
|
||||
providerRepo: providerRepo,
|
||||
logger: logger,
|
||||
logger: pkglogger.WithModule(logger, "service.routing_cache"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"nex/backend/internal/domain"
|
||||
"nex/backend/internal/repository"
|
||||
@@ -119,7 +120,7 @@ func TestModelService_Delete_NotFound(t *testing.T) {
|
||||
|
||||
func TestStatsService_Aggregate_Default(t *testing.T) {
|
||||
statsRepo := repository.NewStatsRepository(nil)
|
||||
buffer := NewStatsBuffer(statsRepo, nil)
|
||||
buffer := NewStatsBuffer(statsRepo, zap.NewNop())
|
||||
svc := NewStatsService(statsRepo, buffer)
|
||||
|
||||
stats := []domain.UsageStats{
|
||||
|
||||
@@ -318,7 +318,7 @@ func TestStatsService_Aggregate_ByModel(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
db := setupServiceTestDB(t)
|
||||
statsRepo := repository.NewStatsRepository(db)
|
||||
buffer := NewStatsBuffer(statsRepo, nil); svc := NewStatsService(statsRepo, buffer)
|
||||
buffer := NewStatsBuffer(statsRepo, zap.NewNop()); svc := NewStatsService(statsRepo, buffer)
|
||||
|
||||
result := svc.Aggregate(tt.stats, "model")
|
||||
|
||||
@@ -379,7 +379,7 @@ func TestStatsService_Aggregate_ByDate(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
db := setupServiceTestDB(t)
|
||||
statsRepo := repository.NewStatsRepository(db)
|
||||
buffer := NewStatsBuffer(statsRepo, nil); svc := NewStatsService(statsRepo, buffer)
|
||||
buffer := NewStatsBuffer(statsRepo, zap.NewNop()); svc := NewStatsService(statsRepo, buffer)
|
||||
|
||||
result := svc.Aggregate(tt.stats, "date")
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
|
||||
"nex/backend/internal/repository"
|
||||
pkglogger "nex/backend/pkg/logger"
|
||||
)
|
||||
|
||||
type StatsBuffer struct {
|
||||
@@ -46,7 +47,7 @@ func NewStatsBuffer(
|
||||
) *StatsBuffer {
|
||||
b := &StatsBuffer{
|
||||
statsRepo: statsRepo,
|
||||
logger: logger,
|
||||
logger: pkglogger.WithModule(logger, "service.stats_buffer"),
|
||||
flushInterval: 5 * time.Second,
|
||||
flushThreshold: 100,
|
||||
stopCh: make(chan struct{}),
|
||||
|
||||
Reference in New Issue
Block a user