Files
freedakgmail 93a469061d
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
CI / security-scan (push) Has been cancelled
初始提交:边缘AI算力机统一AI通讯层
2026-08-03 07:44:05 +08:00

320 lines
7.4 KiB
Go

package observability
import (
"encoding/json"
"fmt"
"os"
"strings"
"sync"
"time"
)
// LogLevel represents logging severity levels.
type LogLevel int
const (
LevelDebug LogLevel = iota
LevelInfo
LevelWarn
LevelError
)
func (l LogLevel) String() string {
switch l {
case LevelDebug:
return "DEBUG"
case LevelInfo:
return "INFO"
case LevelWarn:
return "WARN"
case LevelError:
return "ERROR"
default:
return "INFO"
}
}
// ParseLogLevel parses a string to LogLevel.
func ParseLogLevel(s string) LogLevel {
switch strings.ToLower(s) {
case "debug":
return LevelDebug
case "info":
return LevelInfo
case "warn", "warning":
return LevelWarn
case "error":
return LevelError
default:
return LevelInfo
}
}
// LogEntry is a structured JSON log entry.
type LogEntry struct {
Timestamp string `json:"timestamp"`
Level string `json:"level"`
Event string `json:"event,omitempty"`
Message string `json:"message,omitempty"`
RequestID string `json:"request_id,omitempty"`
TaskID string `json:"task_id,omitempty"`
SessionID string `json:"session_id,omitempty"`
TraceID string `json:"trace_id,omitempty"`
Application string `json:"application,omitempty"`
TenantID string `json:"tenant_id,omitempty"`
UserID string `json:"user_id,omitempty"`
FromState string `json:"from_state,omitempty"`
ToState string `json:"to_state,omitempty"`
Reason string `json:"reason,omitempty"`
LogicalModel string `json:"logical_model,omitempty"`
ActualModel string `json:"actual_model,omitempty"`
NodeID string `json:"node_id,omitempty"`
Degraded bool `json:"degraded,omitempty"`
Extra map[string]any `json:"extra,omitempty"`
}
// Logger is a structured JSON logger with sensitive field masking.
type Logger struct {
mu sync.RWMutex
level LogLevel
output *os.File
maskFields []string
promptLogging string
}
var defaultLogger *Logger
func init() {
defaultLogger = NewLogger(LevelInfo, os.Stdout, "metadata_only")
}
// NewLogger creates a new Logger instance.
func NewLogger(level LogLevel, out *os.File, promptLogging string) *Logger {
return &Logger{
level: level,
output: out,
maskFields: []string{"api_key", "apikey", "authorization", "jwt", "secret", "password", "token"},
promptLogging: promptLogging,
}
}
// GetLogger returns the default logger.
func GetLogger() *Logger {
return defaultLogger
}
// SetLevel updates the log level (thread-safe).
func (l *Logger) SetLevel(level LogLevel) {
l.mu.Lock()
l.level = level
l.mu.Unlock()
}
// SetPromptLogging updates the prompt logging policy.
func (l *Logger) SetPromptLogging(policy string) {
l.mu.Lock()
l.promptLogging = policy
l.mu.Unlock()
}
func (l *Logger) shouldLog(level LogLevel) bool {
l.mu.RLock()
defer l.mu.RUnlock()
return level >= l.level
}
func (l *Logger) maskSensitive(data map[string]any) map[string]any {
if data == nil {
return nil
}
masked := make(map[string]any, len(data))
for k, v := range data {
if l.isSensitive(k) {
masked[k] = "***REDACTED***"
} else if sub, ok := v.(map[string]any); ok {
masked[k] = l.maskSensitive(sub)
} else {
masked[k] = v
}
}
return masked
}
func (l *Logger) isSensitive(key string) bool {
lk := strings.ToLower(key)
for _, s := range l.maskFields {
if strings.Contains(lk, s) {
return true
}
}
return false
}
func (l *Logger) write(entry LogEntry) {
if !l.shouldLog(parseLevelFromString(entry.Level)) {
return
}
if entry.Extra != nil {
entry.Extra = l.maskSensitive(entry.Extra)
}
if entry.Timestamp == "" {
entry.Timestamp = time.Now().UTC().Format(time.RFC3339Nano)
}
data, err := json.Marshal(entry)
if err != nil {
fmt.Fprintf(os.Stderr, "log marshal error: %v\n", err)
return
}
l.mu.Lock()
fmt.Fprintln(l.output, string(data))
l.mu.Unlock()
}
func parseLevelFromString(s string) LogLevel {
switch strings.ToUpper(s) {
case "DEBUG":
return LevelDebug
case "INFO":
return LevelInfo
case "WARN", "WARNING":
return LevelWarn
case "ERROR":
return LevelError
default:
return LevelInfo
}
}
// LogFields is a builder for structured log fields.
type LogFields struct {
fields map[string]any
}
func F() *LogFields {
return &LogFields{fields: make(map[string]any)}
}
func (f *LogFields) Set(key string, value any) *LogFields {
f.fields[key] = value
return f
}
func (f *LogFields) RequestID(id string) *LogFields { f.fields["request_id"] = id; return f }
func (f *LogFields) TaskID(id string) *LogFields { f.fields["task_id"] = id; return f }
func (f *LogFields) SessionID(id string) *LogFields { f.fields["session_id"] = id; return f }
func (f *LogFields) TraceID(id string) *LogFields { f.fields["trace_id"] = id; return f }
func (f *LogFields) Application(app string) *LogFields { f.fields["application"] = app; return f }
func (f *LogFields) TenantID(id string) *LogFields { f.fields["tenant_id"] = id; return f }
func (f *LogFields) UserID(id string) *LogFields { f.fields["user_id"] = id; return f }
func (f *LogFields) Event(e string) *LogFields { f.fields["event"] = e; return f }
func (f *LogFields) Reason(r string) *LogFields { f.fields["reason"] = r; return f }
func (l *Logger) Debug(msg string, fields *LogFields) {
entry := l.buildEntry("DEBUG", msg, fields)
l.write(entry)
}
func (l *Logger) Info(msg string, fields *LogFields) {
entry := l.buildEntry("INFO", msg, fields)
l.write(entry)
}
func (l *Logger) Warn(msg string, fields *LogFields) {
entry := l.buildEntry("WARN", msg, fields)
l.write(entry)
}
func (l *Logger) Error(msg string, fields *LogFields) {
entry := l.buildEntry("ERROR", msg, fields)
l.write(entry)
}
func (l *Logger) buildEntry(level, msg string, fields *LogFields) LogEntry {
entry := LogEntry{
Level: level,
Message: msg,
}
if fields != nil {
for k, v := range fields.fields {
switch k {
case "event":
if s, ok := v.(string); ok {
entry.Event = s
}
case "request_id":
if s, ok := v.(string); ok {
entry.RequestID = s
}
case "task_id":
if s, ok := v.(string); ok {
entry.TaskID = s
}
case "session_id":
if s, ok := v.(string); ok {
entry.SessionID = s
}
case "trace_id":
if s, ok := v.(string); ok {
entry.TraceID = s
}
case "application":
if s, ok := v.(string); ok {
entry.Application = s
}
case "tenant_id":
if s, ok := v.(string); ok {
entry.TenantID = s
}
case "user_id":
if s, ok := v.(string); ok {
entry.UserID = s
}
case "reason":
if s, ok := v.(string); ok {
entry.Reason = s
}
case "from_state":
if s, ok := v.(string); ok {
entry.FromState = s
}
case "to_state":
if s, ok := v.(string); ok {
entry.ToState = s
}
case "logical_model":
if s, ok := v.(string); ok {
entry.LogicalModel = s
}
case "actual_model":
if s, ok := v.(string); ok {
entry.ActualModel = s
}
case "node_id":
if s, ok := v.(string); ok {
entry.NodeID = s
}
case "degraded":
if b, ok := v.(bool); ok {
entry.Degraded = b
}
default:
if entry.Extra == nil {
entry.Extra = make(map[string]any)
}
entry.Extra[k] = v
}
}
}
return entry
}
// SetLogLevel updates the global log level.
func SetLogLevel(level string) {
defaultLogger.SetLevel(ParseLogLevel(level))
}
// SetPromptLoggingPolicy updates the global prompt logging policy.
func SetPromptLoggingPolicy(policy string) {
defaultLogger.SetPromptLogging(policy)
}