268 lines
7.3 KiB
Go
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...)
|
|
}
|