Files
masterlog/logger.go
2025-11-10 05:31:51 +01:00

268 lines
7.3 KiB
Go

package masterlog
import (
"os"
"runtime"
"time"
)
// MasterLogger is the main logger implementation
type MasterLogger struct {
level Level
encoders []Encoder
writers []Writer
pseudonymizer *Pseudonymizer
}
// New creates a new logger with default console writer and formatted encoder
// If level is provided, it will be used; otherwise LevelInfo is the default
func New(level ...Level) *MasterLogger {
logLevel := LevelInfo
if len(level) > 0 {
logLevel = level[0]
}
consoleWriter := NewConsoleWriter()
useColors := consoleWriter.isTerminal
logger := &MasterLogger{
level: logLevel,
encoders: []Encoder{NewFormattedEncoder(useColors)},
writers: []Writer{consoleWriter},
}
return logger
}
// SetLevel sets the minimum log level
func (l *MasterLogger) SetLevel(level Level) {
l.level = level
}
// AddWriter adds a writer to the logger
func (l *MasterLogger) AddWriter(writer Writer) {
l.writers = append(l.writers, writer)
}
// AddEncoder adds an encoder to the logger
func (l *MasterLogger) AddEncoder(encoder Encoder) {
l.encoders = append(l.encoders, encoder)
}
// SetPseudonymizer sets the pseudonymizer for the logger
func (l *MasterLogger) SetPseudonymizer(pseudonymizer *Pseudonymizer) {
l.pseudonymizer = pseudonymizer
}
// AddSensitiveField marks a field name as sensitive, so it will be pseudonymized
func (l *MasterLogger) AddSensitiveField(fieldName string) {
if l.pseudonymizer != nil {
l.pseudonymizer.AddSensitiveField(fieldName)
}
}
// AddSensitiveFields marks multiple field names as sensitive
func (l *MasterLogger) AddSensitiveFields(fieldNames ...string) {
if l.pseudonymizer != nil {
l.pseudonymizer.AddSensitiveFields(fieldNames...)
}
}
// log writes a log entry
// If file and line are empty/zero, they will be determined from the call stack
func (l *MasterLogger) log(level Level, message string, file string, line int, fields ...map[string]interface{}) {
if level < l.level {
return
}
// If file/line not provided, get from call stack
if file == "" && line == 0 {
// For instance methods: 0=Caller, 1=log(), 2=Info(), 3=user code
_, file, line, _ = runtime.Caller(3)
if file == "" {
file = "unknown"
}
}
// Collect custom fields (user-provided)
customFields := make(map[string]interface{})
for _, fieldMap := range fields {
for k, v := range fieldMap {
customFields[k] = v
}
}
// Collect default fields
defaultFields := map[string]interface{}{
"go_version": runtime.Version(),
"pid": os.Getpid(),
}
// Apply pseudonymization if enabled
if l.pseudonymizer != nil {
customFields = l.pseudonymizer.PseudonymizeFields(customFields)
defaultFields = l.pseudonymizer.PseudonymizeFields(defaultFields)
}
// Merge all fields for compatibility
allFields := make(map[string]interface{})
for k, v := range customFields {
allFields[k] = v
}
for k, v := range defaultFields {
allFields[k] = v
}
entry := Entry{
Timestamp: time.Now(),
Level: level,
File: file,
Line: line,
Message: message,
Fields: allFields,
CustomFields: customFields,
DefaultFields: defaultFields,
}
// Encode and write with each encoder/writer combination
// For formatted encoder, we need to encode differently for each writer (with/without colors)
for _, encoder := range l.encoders {
for _, writer := range l.writers {
// Skip JSON encoder for console writers (only use formatted encoder)
if _, isJSON := encoder.(*JSONEncoder); isJSON {
if _, isConsole := writer.(*ConsoleWriter); isConsole {
continue // Don't write JSON to console
}
}
// Skip FormattedEncoder for file writers if JSONEncoder is present
// This ensures files only get JSON format when JSONEncoder is added
if _, isFormatted := encoder.(*FormattedEncoder); isFormatted {
if _, isFile := writer.(*FileWriter); isFile {
// Check if JSONEncoder exists in encoders
hasJSONEncoder := false
for _, e := range l.encoders {
if _, ok := e.(*JSONEncoder); ok {
hasJSONEncoder = true
break
}
}
if hasJSONEncoder {
continue // Don't write formatted to file if JSON encoder exists
}
}
}
var data []byte
var err error
// If it's a FormattedEncoder, check if writer supports colors
if _, ok := encoder.(*FormattedEncoder); ok {
// Create a temporary encoder with the right color setting
tempEncoder := &FormattedEncoder{useColors: writer.SupportsColors()}
data, err = tempEncoder.Encode(entry)
} else {
// For other encoders (JSON, etc.), use as-is
data, err = encoder.Encode(entry)
}
if err != nil {
continue
}
_ = writer.Write(data)
}
}
}
func (l *MasterLogger) Trace(message string, fields ...map[string]interface{}) {
_, file, line, _ := runtime.Caller(1)
l.log(LevelTrace, message, file, line, fields...)
}
func (l *MasterLogger) Debug(message string, fields ...map[string]interface{}) {
_, file, line, _ := runtime.Caller(1)
l.log(LevelDebug, message, file, line, fields...)
}
func (l *MasterLogger) Info(message string, fields ...map[string]interface{}) {
_, file, line, _ := runtime.Caller(1)
l.log(LevelInfo, message, file, line, fields...)
}
func (l *MasterLogger) Warn(message string, fields ...map[string]interface{}) {
_, file, line, _ := runtime.Caller(1)
l.log(LevelWarn, message, file, line, fields...)
}
func (l *MasterLogger) Error(message string, fields ...map[string]interface{}) {
_, file, line, _ := runtime.Caller(1)
l.log(LevelError, message, file, line, fields...)
}
// Default logger instance
var defaultLogger = New(LevelInfo)
// SetDefaultLogger replaces the default logger instance
// This allows you to configure a custom logger and use it globally
func SetDefaultLogger(logger *MasterLogger) {
defaultLogger = logger
}
// GetDefaultLogger returns the current default logger instance
// Useful if you want to configure it directly
func GetDefaultLogger() *MasterLogger {
return defaultLogger
}
// Package-level convenience functions
// These collect caller info before calling the instance method
func Trace(message string, fields ...map[string]interface{}) {
_, file, line, _ := runtime.Caller(1)
defaultLogger.log(LevelTrace, message, file, line, fields...)
}
func Debug(message string, fields ...map[string]interface{}) {
_, file, line, _ := runtime.Caller(1)
defaultLogger.log(LevelDebug, message, file, line, fields...)
}
func Info(message string, fields ...map[string]interface{}) {
_, file, line, _ := runtime.Caller(1)
defaultLogger.log(LevelInfo, message, file, line, fields...)
}
func Warn(message string, fields ...map[string]interface{}) {
_, file, line, _ := runtime.Caller(1)
defaultLogger.log(LevelWarn, message, file, line, fields...)
}
func Error(message string, fields ...map[string]interface{}) {
_, file, line, _ := runtime.Caller(1)
defaultLogger.log(LevelError, message, file, line, fields...)
}
func SetLevel(level Level) {
defaultLogger.SetLevel(level)
}
func AddWriter(writer Writer) {
defaultLogger.AddWriter(writer)
}
func AddEncoder(encoder Encoder) {
defaultLogger.AddEncoder(encoder)
}
func SetPseudonymizer(pseudonymizer *Pseudonymizer) {
defaultLogger.SetPseudonymizer(pseudonymizer)
}
func AddSensitiveField(fieldName string) {
defaultLogger.AddSensitiveField(fieldName)
}
func AddSensitiveFields(fieldNames ...string) {
defaultLogger.AddSensitiveFields(fieldNames...)
}