Files
xlgo-core/console/console.go
T
杭州明婳科技 6595e94677 fix: 多模型评审终审收口 — P0安全 + 主线A并发治理 + 文档对齐
经 deepseek/GLM/Claude 三轮对抗性评审 + 交叉核验 + 终审(见
last_report.md、cross_review_assessment.md),逐条回读源码核实并修复。
go build / vet / test -race ./... 全绿。

安全(P0):
- JWT alg confusion:WithValidMethods 固定 HMAC + keyfunc 方法断言
- JWT 空密钥 / 不支持算法 fail-closed(ErrEmptySecret / ErrUnsupportedAlgorithm)
- 上传大小实测封顶(enforceUploadSize),不信任客户端 file.Size
- HTTP headers/cookies map 竞态(写锁+快照)+ SSRF 防护(opt-in dialer.Control)

并发/资源(主线A 闭合):
- 包级可变全局一律 atomic.Pointer:DefaultRedis / DefaultStorage /
  DefaultManager / Validator / DefaultCache 等,消除裸指针数据竞争
- trace.Close 去 sync.Once(防 exporter 泄漏);app OnReady 失败走 Shutdown
- ws.Hub.Stop stopOnce 防 double-close;cron 接入 App 生命周期(WithCron)
- ratelimit failClosed atomic.Bool;Recover Written() 守卫
- cron checkAndRun 锁内收集锁外 spawn(wg.Add 在锁内防 Stop 竞态)

终审剩余项收口(H-A~M-H + L 系列):
- 副本连接池 MaxOpenConns/2 截断修复(replicaMaxOpenConns)
- JWT 黑名单 1s 超时 + 显式错误;ratelimit GetRedis 取一次复用
- GetPage 深分页上限;HashFile 流式;ReadFile 去 TOCTOU
- config Clone() 深拷贝;cache 锁操作返 ErrRedisNotReady
- compress 解压残留清理;正则预编译;EqualsIgnoreCase→EqualFold
- 删 redisLimiters 死代码;CLI 输入校验 + 回滚

文档:
- README/GUIDE/CHANGELOG 对齐当前 API(含破坏性变更迁移说明)
- docs/README 索引修正;config 注释修正(RS256 已不支持)

破坏性变更详见 CHANGELOG.md [Unreleased]。项目处于研发初期、无下游用户。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:16:03 +08:00

288 lines
6.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package console
import (
"fmt"
"io"
"os"
"runtime"
"strings"
"sync"
"sync/atomic"
"time"
)
// writeMu 串行化所有 Console 的写出序列(P1 #7)。
// 用包级锁而非 per-Console 锁:多个 Console 实例可能共享同一底层 writer(如 stdout),
// per-Console 锁无法阻止跨实例交错;且 Windows printColor 的"设色→写→复位"三步必须整体
// 原子,否则并发彩色输出会串色。控制台输出非热路径,单一全局写锁的串行化开销可忽略。
var writeMu sync.Mutex
// Level 日志级别
type Level int32
const (
LevelDebug Level = iota
LevelInfo
LevelSuccess
LevelWarn
LevelError
// LevelSilent 完全静默:所有调用都不输出
LevelSilent Level = 127
)
// String 返回级别名称
func (l Level) String() string {
if c, ok := colors[l]; ok {
return c.Name
}
if l == LevelSilent {
return "Silent"
}
return "Unknown"
}
// Color 颜色定义
type Color struct {
Code string
Name string
}
var colors = map[Level]Color{
LevelDebug: {Code: "0;36", Name: "Debug"}, // 青色
LevelInfo: {Code: "0;37", Name: "Info"}, // 白色
LevelSuccess: {Code: "0;92", Name: "Success"}, // 亮绿色
LevelWarn: {Code: "1;93", Name: "Warn"}, // 亮黄色
LevelError: {Code: "1;31", Name: "Error"}, // 亮红色
}
// Console 控制台打印器。
//
// console 包定位:开发期彩色 stdout 工具,跟 fmt.Println 同级。
// 不写文件、不感知运行环境、不做任何隐式 level 切换——
// 所有 level 行为都由调用方显式控制(SetLevel / WithLevel)。
//
// 业务可观测信息(用户登录、订单状态变更等"上线后必须保留的事件")
// 请使用 logger 包;console 仅用于开发期肉眼调试。
type Console struct {
output io.Writer
isColor bool
showTime bool
showCall bool
timeFmt string
skipCall int
// level 通过 atomic 访问,支持运行期热切换且并发安全。
// 用 int32 存储 Level0 = LevelDebug,与零值默认对齐。
level atomic.Int32
}
// Option 配置选项
type Option func(*Console)
// WithOutput 设置输出目标
func WithOutput(w io.Writer) Option {
return func(c *Console) {
c.output = w
}
}
// WithColor 设置是否启用颜色
func WithColor(enable bool) Option {
return func(c *Console) {
c.isColor = enable
}
}
// WithTime 设置是否显示时间
func WithTime(show bool) Option {
return func(c *Console) {
c.showTime = show
}
}
// WithCaller 设置是否显示调用位置。
// skip 可选,默认 2(直接调用方);自封装一层时传 3。
func WithCaller(show bool, skip ...int) Option {
return func(c *Console) {
c.showCall = show
if len(skip) > 0 && skip[0] > 0 {
c.skipCall = skip[0]
}
}
}
// WithTimeFormat 设置时间格式
func WithTimeFormat(fmt string) Option {
return func(c *Console) {
c.timeFmt = fmt
}
}
// WithLevel 设置最低输出级别。低于该级别的调用会被静默丢弃。
//
// 例:WithLevel(LevelWarn) 只输出 Warn 与 Error
//
// WithLevel(LevelSilent) 完全静默,常用于压测或上线观察期临时关闭调试输出。
func WithLevel(l Level) Option {
return func(c *Console) {
c.level.Store(int32(l))
}
}
// New 创建控制台打印器
func New(opts ...Option) *Console {
c := &Console{
output: os.Stdout,
isColor: true,
showTime: true,
showCall: true,
timeFmt: "15:04:05.000",
skipCall: 2,
}
// 默认 LevelDebug:开发期所有级别都打印。生产期请显式 SetLevel/WithLevel。
c.level.Store(int32(LevelDebug))
for _, opt := range opts {
opt(c)
}
return c
}
// SetLevel 运行期切换最低输出级别。并发安全。
func (c *Console) SetLevel(l Level) {
c.level.Store(int32(l))
}
// Level 返回当前最低输出级别
func (c *Console) Level() Level {
return Level(c.level.Load())
}
// Default 默认控制台
var Default = New()
// SetLevel 设置默认控制台的最低输出级别。并发安全。
//
// 典型用法(在 main 中根据 cfg 显式切换):
//
// if cfg.IsProduction() {
// console.SetLevel(console.LevelWarn) // 生产期只看 Warn / Error
// }
//
// 框架不会自动根据环境模式切换,选择权完全在调用方。
func SetLevel(l Level) {
Default.SetLevel(l)
}
// GetLevel 返回默认控制台当前最低输出级别。
// (命名加 Get 前缀是因为 Level 已被类型占用,Go 不允许同名函数。)
func GetLevel() Level {
return Default.Level()
}
// print 内部打印函数
func (c *Console) print(level Level, s ...any) {
// 级别过滤:低于阈值或调用方显式 LevelSilent 时直接返回,零开销
if level < c.Level() {
return
}
var sb strings.Builder
// 时间
if c.showTime {
sb.WriteString(time.Now().Format(c.timeFmt))
sb.WriteString(" ")
}
// 级别名称
color := colors[level]
sb.WriteString("[")
sb.WriteString(color.Name)
sb.WriteString("] ")
// 调用位置
if c.showCall {
sb.WriteString(c.getCaller())
sb.WriteString(" ")
}
// 内容
sb.WriteString(fmt.Sprint(s...))
// 输出。P1 #7:全局写锁串行化整个写序列,避免并发交错输出;
// Windows 下 printColor 的"设色→写→复位"三步也被此锁整体保护,防止串色。
writeMu.Lock()
if c.isColor {
c.printColor(color.Code, sb.String())
} else {
fmt.Fprintln(c.output, sb.String())
}
writeMu.Unlock()
}
// getCaller 获取调用位置
func (c *Console) getCaller() string {
_, file, line, ok := runtime.Caller(c.skipCall)
if !ok {
return ""
}
// 只取文件名(兼容 / 与 \ 分隔)
if idx := strings.LastIndexAny(file, "/\\"); idx >= 0 {
file = file[idx+1:]
}
return fmt.Sprintf("%s:%d", file, line)
}
// Debug 打印调试信息(青色)
func (c *Console) Debug(s ...any) {
c.print(LevelDebug, s...)
}
// Info 打印普通信息(白色)
func (c *Console) Info(s ...any) {
c.print(LevelInfo, s...)
}
// Success 打印成功信息(绿色)
func (c *Console) Success(s ...any) {
c.print(LevelSuccess, s...)
}
// Warn 打印警告信息(黄色)
func (c *Console) Warn(s ...any) {
c.print(LevelWarn, s...)
}
// Error 打印错误信息(红色)
func (c *Console) Error(s ...any) {
c.print(LevelError, s...)
}
// ===== 包级别便捷函数 =====
// Debug 使用默认控制台打印调试信息
func Debug(s ...any) {
Default.print(LevelDebug, s...)
}
// Info 使用默认控制台打印普通信息
func Info(s ...any) {
Default.print(LevelInfo, s...)
}
// Success 使用默认控制台打印成功信息
func Success(s ...any) {
Default.print(LevelSuccess, s...)
}
// Warn 使用默认控制台打印警告信息
func Warn(s ...any) {
Default.print(LevelWarn, s...)
}
// Error 使用默认控制台打印错误信息
func Error(s ...any) {
Default.print(LevelError, s...)
}