2026-04-11 09:45:37 +08:00
|
|
|
|
package logging
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"encoding/json"
|
2026-04-13 07:46:52 +08:00
|
|
|
|
"fmt"
|
2026-04-11 09:45:37 +08:00
|
|
|
|
"os"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== P1-010 日志规范 ====================
|
|
|
|
|
|
|
|
|
|
|
|
// LogLevel 日志级别
|
|
|
|
|
|
type LogLevel string
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
LogLevelDebug LogLevel = "DEBUG"
|
|
|
|
|
|
LogLevelInfo LogLevel = "INFO"
|
|
|
|
|
|
LogLevelWarn LogLevel = "WARN"
|
|
|
|
|
|
LogLevelError LogLevel = "ERROR"
|
|
|
|
|
|
LogLevelFatal LogLevel = "FATAL"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// LogEntry 标准日志条目(JSON格式)
|
|
|
|
|
|
type LogEntry struct {
|
|
|
|
|
|
Timestamp string `json:"timestamp"` // ISO8601格式
|
|
|
|
|
|
Level string `json:"level"` // DEBUG|INFO|WARN|ERROR|FATAL
|
|
|
|
|
|
Service string `json:"service"` // 服务名称
|
|
|
|
|
|
TraceID string `json:"trace_id,omitempty"` // 追踪ID
|
|
|
|
|
|
SpanID string `json:"span_id,omitempty"` // Span ID
|
|
|
|
|
|
RequestID string `json:"request_id,omitempty"` // 请求ID
|
|
|
|
|
|
Message string `json:"message"` // 日志消息
|
|
|
|
|
|
Fields map[string]interface{} `json:"fields,omitempty"` // 额外字段
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Logger 日志接口
|
|
|
|
|
|
type Logger interface {
|
|
|
|
|
|
Debug(msg string, fields ...map[string]interface{})
|
|
|
|
|
|
Info(msg string, fields ...map[string]interface{})
|
|
|
|
|
|
Warn(msg string, fields ...map[string]interface{})
|
|
|
|
|
|
Error(msg string, fields ...map[string]interface{})
|
|
|
|
|
|
Fatal(msg string, fields ...map[string]interface{})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// jsonLogger JSON格式日志实现
|
|
|
|
|
|
type jsonLogger struct {
|
|
|
|
|
|
service string
|
|
|
|
|
|
minLevel LogLevel
|
|
|
|
|
|
output *os.File
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewLogger 创建日志实例
|
|
|
|
|
|
func NewLogger(service string, minLevel LogLevel) *jsonLogger {
|
|
|
|
|
|
return &jsonLogger{
|
|
|
|
|
|
service: service,
|
|
|
|
|
|
minLevel: minLevel,
|
|
|
|
|
|
output: os.Stdout,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// shouldLog 检查是否应该记录此级别
|
|
|
|
|
|
func (l *jsonLogger) shouldLog(level LogLevel) bool {
|
|
|
|
|
|
levels := map[LogLevel]int{
|
|
|
|
|
|
LogLevelDebug: 0,
|
|
|
|
|
|
LogLevelInfo: 1,
|
|
|
|
|
|
LogLevelWarn: 2,
|
|
|
|
|
|
LogLevelError: 3,
|
|
|
|
|
|
LogLevelFatal: 4,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return levels[level] >= levels[l.minLevel]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// formatEntry 格式化日志条目
|
|
|
|
|
|
func (l *jsonLogger) formatEntry(level LogLevel, msg string, fields map[string]interface{}) *LogEntry {
|
|
|
|
|
|
entry := &LogEntry{
|
|
|
|
|
|
Timestamp: time.Now().UTC().Format(time.RFC3339Nano),
|
|
|
|
|
|
Level: string(level),
|
|
|
|
|
|
Service: l.service,
|
|
|
|
|
|
Message: msg,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加fields
|
|
|
|
|
|
if fields != nil {
|
|
|
|
|
|
entry.Fields = sanitizeFields(fields)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return entry
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// log 输出日志
|
|
|
|
|
|
func (l *jsonLogger) log(level LogLevel, msg string, fields map[string]interface{}) {
|
|
|
|
|
|
if !l.shouldLog(level) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
entry := l.formatEntry(level, msg, fields)
|
|
|
|
|
|
|
|
|
|
|
|
// 序列化为JSON
|
|
|
|
|
|
data, err := json.Marshal(entry)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加换行符
|
|
|
|
|
|
l.output.Write(append(data, '\n'))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (l *jsonLogger) Debug(msg string, fields ...map[string]interface{}) {
|
|
|
|
|
|
var f map[string]interface{}
|
|
|
|
|
|
if len(fields) > 0 {
|
|
|
|
|
|
f = fields[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
l.log(LogLevelDebug, msg, f)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (l *jsonLogger) Info(msg string, fields ...map[string]interface{}) {
|
|
|
|
|
|
var f map[string]interface{}
|
|
|
|
|
|
if len(fields) > 0 {
|
|
|
|
|
|
f = fields[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
l.log(LogLevelInfo, msg, f)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (l *jsonLogger) Warn(msg string, fields ...map[string]interface{}) {
|
|
|
|
|
|
var f map[string]interface{}
|
|
|
|
|
|
if len(fields) > 0 {
|
|
|
|
|
|
f = fields[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
l.log(LogLevelWarn, msg, f)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (l *jsonLogger) Error(msg string, fields ...map[string]interface{}) {
|
|
|
|
|
|
var f map[string]interface{}
|
|
|
|
|
|
if len(fields) > 0 {
|
|
|
|
|
|
f = fields[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
l.log(LogLevelError, msg, f)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (l *jsonLogger) Fatal(msg string, fields ...map[string]interface{}) {
|
|
|
|
|
|
var f map[string]interface{}
|
|
|
|
|
|
if len(fields) > 0 {
|
|
|
|
|
|
f = fields[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
l.log(LogLevelFatal, msg, f)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 07:46:52 +08:00
|
|
|
|
// Infof 格式化信息日志
|
|
|
|
|
|
func (l *jsonLogger) Infof(format string, args ...interface{}) {
|
|
|
|
|
|
l.Info(fmt.Sprintf(format, args...), nil)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Errorf 格式化错误日志
|
|
|
|
|
|
func (l *jsonLogger) Errorf(format string, args ...interface{}) {
|
|
|
|
|
|
l.Error(fmt.Sprintf(format, args...), nil)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Warnf 格式化警告日志
|
|
|
|
|
|
func (l *jsonLogger) Warnf(format string, args ...interface{}) {
|
|
|
|
|
|
l.Warn(fmt.Sprintf(format, args...), nil)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Debugf 格式化调试日志
|
|
|
|
|
|
func (l *jsonLogger) Debugf(format string, args ...interface{}) {
|
|
|
|
|
|
l.Debug(fmt.Sprintf(format, args...), nil)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Fatalf 格式化致命日志
|
|
|
|
|
|
func (l *jsonLogger) Fatalf(format string, args ...interface{}) {
|
|
|
|
|
|
l.Fatal(fmt.Sprintf(format, args...), nil)
|
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-11 09:45:37 +08:00
|
|
|
|
// sanitizeFields 敏感字段脱敏
|
|
|
|
|
|
func sanitizeFields(fields map[string]interface{}) map[string]interface{} {
|
|
|
|
|
|
sanitized := make(map[string]interface{})
|
|
|
|
|
|
|
|
|
|
|
|
sensitiveKeys := []string{
|
|
|
|
|
|
"password", "secret", "token", "api_key", "apikey",
|
|
|
|
|
|
"credential", "authorization", "private_key",
|
|
|
|
|
|
"credit_card", "ssn", "passport",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for k, v := range fields {
|
|
|
|
|
|
lowerK := toLower(k)
|
|
|
|
|
|
for _, sensitive := range sensitiveKeys {
|
|
|
|
|
|
if contains(lowerK, sensitive) {
|
|
|
|
|
|
sanitized[k] = "[REDACTED]"
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if _, ok := sanitized[k]; !ok {
|
|
|
|
|
|
// 检查嵌套map
|
|
|
|
|
|
if nestedMap, ok := v.(map[string]interface{}); ok {
|
|
|
|
|
|
sanitized[k] = sanitizeFields(nestedMap)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
sanitized[k] = v
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return sanitized
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// String helpers
|
|
|
|
|
|
func toLower(s string) string {
|
|
|
|
|
|
result := make([]byte, len(s))
|
|
|
|
|
|
for i := 0; i < len(s); i++ {
|
|
|
|
|
|
c := s[i]
|
|
|
|
|
|
if c >= 'A' && c <= 'Z' {
|
|
|
|
|
|
c += 'a' - 'A'
|
|
|
|
|
|
}
|
|
|
|
|
|
result[i] = c
|
|
|
|
|
|
}
|
|
|
|
|
|
return string(result)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func contains(s, substr string) bool {
|
|
|
|
|
|
for i := 0; i <= len(s)-len(substr); i++ {
|
|
|
|
|
|
if s[i:i+len(substr)] == substr {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// LogFieldKeys 日志字段名常量(防止拼写错误)
|
|
|
|
|
|
const (
|
|
|
|
|
|
FieldKeyTenantID = "tenant_id"
|
|
|
|
|
|
FieldKeyUserID = "user_id"
|
|
|
|
|
|
FieldKeyRequestID = "request_id"
|
|
|
|
|
|
FieldKeyTraceID = "trace_id"
|
|
|
|
|
|
FieldKeySpanID = "span_id"
|
|
|
|
|
|
FieldKeyOperation = "operation"
|
|
|
|
|
|
FieldKeyDuration = "duration_ms"
|
|
|
|
|
|
FieldKeyStatusCode = "status_code"
|
|
|
|
|
|
FieldKeyError = "error"
|
|
|
|
|
|
FieldKeyErrorCode = "error_code"
|
|
|
|
|
|
FieldKeyClientIP = "client_ip"
|
|
|
|
|
|
FieldKeyUserAgent = "user_agent"
|
|
|
|
|
|
FieldKeyMethod = "method"
|
|
|
|
|
|
FieldKeyPath = "path"
|
|
|
|
|
|
FieldKeyQuery = "query"
|
|
|
|
|
|
FieldKeyRoute = "route"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 标准字段常量
|
|
|
|
|
|
var SensitiveFields = []string{
|
|
|
|
|
|
"password",
|
|
|
|
|
|
"secret",
|
|
|
|
|
|
"token",
|
|
|
|
|
|
"api_key",
|
|
|
|
|
|
"apikey",
|
|
|
|
|
|
"credential",
|
|
|
|
|
|
"authorization",
|
|
|
|
|
|
"private_key",
|
|
|
|
|
|
"credit_card",
|
|
|
|
|
|
"ssn",
|
|
|
|
|
|
}
|