diff --git a/configs/config.yaml b/configs/config.yaml index 493d9c7..067a761 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -128,7 +128,10 @@ admin: access_resp_data: true access_req_headers: true access_log_max_bytes: 1024 - file_only_modules: [] + # High-volume request and SQL details remain available in logs/http and logs/sql. + file_only_modules: + - http + - sql cors: mode: whitelist whitelist: [] diff --git a/internal/server/gin.go b/internal/server/gin.go index 4623edb..8f44263 100644 --- a/internal/server/gin.go +++ b/internal/server/gin.go @@ -6,7 +6,6 @@ import ( "net/http" "os" "path" - "sort" "strings" "time" @@ -79,28 +78,9 @@ func NewGinEngineWithRuntime(runtime *conf.Runtime, access *service.AccessContro } httpx.Fail(c, "请求的接口不存在") }) - logRegisteredRoutes(engine, logger) return engine } -func logRegisteredRoutes(engine *gin.Engine, logger *slog.Logger) { - if logger == nil { - return - } - routes := append([]gin.RouteInfo(nil), engine.Routes()...) - sort.Slice(routes, func(i, j int) bool { - if routes[i].Path == routes[j].Path { - return routes[i].Method < routes[j].Method - } - return routes[i].Path < routes[j].Path - }) - systemLogger := logger.With("mod", "system") - for _, route := range routes { - systemLogger.Info("router registered", "method", route.Method, "path", route.Path) - } - systemLogger.Info("router register success", "route_count", len(routes)) -} - func NewGinServer(c *conf.Server, engine *gin.Engine) *kratoshttp.Server { network, address := "tcp", ":8000" if c != nil && c.Http != nil { diff --git a/pkg/logging/zap.go b/pkg/logging/zap.go index caffb1f..42ff57d 100644 --- a/pkg/logging/zap.go +++ b/pkg/logging/zap.go @@ -433,7 +433,18 @@ func newZapHandler(root, filename string, options Options, errorSink *errorSinkS for _, module := range options.FileOnlyModules { fileOnly[module] = struct{}{} } - consoleCore := zapcore.NewCore(outputEncoder.Clone(), zapcore.AddSync(os.Stdout), levelEnabler) + // Files stay structured JSON for searching and the log viewer. The console + // gets a compact human-readable encoder so entries do not become one long + // JSON line that extends far beyond the terminal viewport. + consoleEncoderConfig := zap.NewProductionEncoderConfig() + consoleEncoderConfig.EncodeTime = func(value time.Time, output zapcore.PrimitiveArrayEncoder) { + output.AppendString(options.Prefix + value.Format("2006-01-02 15:04:05.000")) + } + consoleEncoderConfig.EncodeLevel = zapcore.LowercaseColorLevelEncoder + consoleEncoderConfig.EncodeCaller = zapcore.FullCallerEncoder + consoleEncoderConfig.ConsoleSeparator = " " + consoleEncoder := zapcore.NewConsoleEncoder(consoleEncoderConfig) + consoleCore := zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), levelEnabler) core = zapcore.NewTee(fileCore, &moduleFilterCore{Core: consoleCore, fileOnly: fileOnly}) } routed := &routedFileCore{base: core, encoder: outputEncoder.Clone(), level: levelEnabler, root: root, retentionDay: options.RetentionDay, state: &routedFileState{writers: map[string]*DailyWriter{}}, errorSink: errorSink} diff --git a/pkg/logging/zap_test.go b/pkg/logging/zap_test.go index 63c5a7f..9b84c11 100644 --- a/pkg/logging/zap_test.go +++ b/pkg/logging/zap_test.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" "testing" + "time" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -61,6 +62,21 @@ func TestZapHandlerRoutesHTTPAndErrorLogs(t *testing.T) { } } +func TestConsoleEncoderDoesNotChangeJSONFileEncoder(t *testing.T) { + config := zap.NewProductionEncoderConfig() + config.EncodeTime = zapcore.RFC3339NanoTimeEncoder + fileEncoder := zapcore.NewJSONEncoder(config) + entry := zapcore.Entry{Level: zapcore.InfoLevel, Time: time.Date(2026, 8, 21, 16, 30, 0, 0, time.Local), Message: "started"} + buffer, err := fileEncoder.EncodeEntry(entry, nil) + if err != nil { + t.Fatal(err) + } + defer buffer.Free() + if !strings.HasPrefix(buffer.String(), "{") || !strings.Contains(buffer.String(), `"msg":"started"`) { + t.Fatalf("file encoder is no longer JSON: %s", buffer.String()) + } +} + func TestZapHandlerRecordsEveryErrorThroughSink(t *testing.T) { root := t.TempDir() var entries []ErrorEntry diff --git a/web/src/view/systemTools/system/system.vue b/web/src/view/systemTools/system/system.vue index 39c344a..a747cf3 100644 --- a/web/src/view/systemTools/system/system.vue +++ b/web/src/view/systemTools/system/system.vue @@ -257,7 +257,7 @@ jwt: { signingKey: '******', expiresTime: '168h', bufferTime: '24h', issuer: 'kra' }, captcha: { keyLong: 6, imgWidth: 240, imgHeight: 80, storeExpiration: '3m' }, local: { storePath: 'uploads/file', pathPrefix: 'uploads/file' }, media: { sessionTtl: 24, maxFileSize: 0, chunkDir: 'uploads/chunks' }, - zap: { level: 'info', prefix: '[kra] ', format: 'json', director: 'logs', encode_level: 'LowercaseLevelEncoder', stacktrace_key: 'stacktrace', show_line: true, log_in_console: true, retention_day: 7, access_req_body: true, access_resp_data: true, access_req_headers: true, access_log_max_bytes: 1024, file_only_modules: [] }, + zap: { level: 'info', prefix: '[kra] ', format: 'json', director: 'logs', encode_level: 'LowercaseLevelEncoder', stacktrace_key: 'stacktrace', show_line: true, log_in_console: true, retention_day: 7, access_req_body: true, access_resp_data: true, access_req_headers: true, access_log_max_bytes: 1024, file_only_modules: ['http', 'sql'] }, cors: { mode: 'whitelist', whitelist: [] }, app: { node: '', app_id: 'kra', env: 'development' }, system: { useRedis: false, useMultipoint: false, useStrictAuth: false, disableAutoMigrate: false, useMongo: false, addr: 8000, iplimitCount: 0, iplimitTime: 0 }, storage: { type: 'local', qiniu: {}, aliyun_oss: {}, huawei_obs: {}, tencent_cos: {}, aws_s3: {}, cloudflare_r2: {}, minio: {} }