dcfd24b624
v1.0.3 定位为 bug fix release,收口 v1.0.2 引入的破坏性清理并修复 4 个轻量 bug + 依赖复查 + 版本号治理 + 文档对齐。同时包含此前未提交 的 v1.0.2 全部工作。 v1.0.3 变更: Fixed: - generateJTI 忽略 rand.Read 错误(jwt)— 改为 (string, error) 并传播 - QueryBuilder.Page Count 受残留 Limit 截断(repository)— countDB 加 Limit(-1).Offset(-1) - OSS/本地存储文件名冲突(storage)— 新增 uniqueFilename 加 8 字节随机后缀 - 数据库重试策略对不可恢复错误无效(database)— 新增 isTransientDBError Dependencies: - go mod tidy 补全 postgres 方言传递依赖;安全补丁升级 x/crypto v0.49→v0.53、golang-jwt/jwt/v5 v5.2.1→v5.3.1、gorilla/websocket v1.5.1→v1.5.3 Removed: - Breaking: 清理 v1.0.2 兼容别名(InitMySQL* / driverName) - 死代码 database.DBResolver - 代码中 292 行"评分/理由"自夸注释(#26,23 个文件) Changed: - Breaking: 错误码体系重构 CodeSuccess 1→0、CodeFail 0→1,删除 CodeInvalidParams,加编译期防撞码 - database/mysql.go → manager.go;Logger 拆分三独立 core 修复 Tee 重复写入 - 版本号常量化:app.go 新增 const Version 作为唯一来源,CLI/脚手架模板引用之,不再散落字面量 Security: - CORS 中间件按 W3C 规范修复 Allow-Credentials / Vary: Origin / 非白名单不回显 Added: - console 包显式 level 控制(SetLevel/WithLevel/LevelSilent,atomic.Int32 并发安全) - 新增测试 jwt/repository/storage/database/router/middleware/app - 文档:新增 CHANGELOG.md、Version_v1.0.2_report.md;更新 README/GUIDE 顶部更新日志 - 文档对齐:删除对不存在的 config.DefaultManager() getter 的描述(保持 API 与文档一致) 详见 CHANGELOG.md 与 README.md 更新日志。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
279 lines
6.0 KiB
Go
279 lines
6.0 KiB
Go
package console
|
||
|
||
import (
|
||
"fmt"
|
||
"io"
|
||
"os"
|
||
"runtime"
|
||
"strings"
|
||
"sync/atomic"
|
||
"time"
|
||
)
|
||
|
||
// 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 存储 Level,0 = 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...))
|
||
|
||
// 输出
|
||
if c.isColor {
|
||
c.printColor(color.Code, sb.String())
|
||
} else {
|
||
fmt.Fprintln(c.output, sb.String())
|
||
}
|
||
}
|
||
|
||
// getCaller 获取调用位置
|
||
func (c *Console) getCaller() string {
|
||
_, file, line, ok := runtime.Caller(c.skipCall)
|
||
if !ok {
|
||
return ""
|
||
}
|
||
// 只取文件名
|
||
idx := strings.LastIndex(file, "/")
|
||
if 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...)
|
||
}
|