Files
lijiaoqiao/supply-api/internal/pkg/logging/logger.go
Your Name 90fc95bc27 fix(supply-api): add missing runtime support sources
Check in the healthcheck, structured logging, outbox broker, partition manager, and token status repository files that the committed supply-api runtime already imports. Verified with fresh go test runs for cmd/supply-api, internal/httpapi, internal/pkg/logging, internal/repository, and internal/outbox.
2026-04-11 09:45:37 +08:00

234 lines
5.4 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 logging
import (
"encoding/json"
"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)
}
// 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",
}