Files
atlas/internal/logger/logger.go
othman.suseno df475bc85e
Some checks failed
CI / test-build (push) Failing after 2m11s
logging and diagnostic features added
2025-12-15 00:45:14 +07:00

216 lines
4.5 KiB
Go

package logger
import (
"encoding/json"
"fmt"
"io"
"os"
"sync"
"time"
)
// Level represents log level
type Level int
const (
LevelDebug Level = iota
LevelInfo
LevelWarn
LevelError
)
var levelNames = map[Level]string{
LevelDebug: "DEBUG",
LevelInfo: "INFO",
LevelWarn: "WARN",
LevelError: "ERROR",
}
// Logger provides structured logging
type Logger struct {
mu sync.Mutex
level Level
output io.Writer
jsonMode bool
prefix string
}
// LogEntry represents a structured log entry
type LogEntry struct {
Timestamp string `json:"timestamp"`
Level string `json:"level"`
Message string `json:"message"`
Fields map[string]interface{} `json:"fields,omitempty"`
Error string `json:"error,omitempty"`
}
// New creates a new logger
func New(level Level, output io.Writer, jsonMode bool) *Logger {
if output == nil {
output = os.Stdout
}
return &Logger{
level: level,
output: output,
jsonMode: jsonMode,
}
}
// SetLevel sets the log level
func (l *Logger) SetLevel(level Level) {
l.mu.Lock()
defer l.mu.Unlock()
l.level = level
}
// SetOutput sets the output writer
func (l *Logger) SetOutput(w io.Writer) {
l.mu.Lock()
defer l.mu.Unlock()
l.output = w
}
// Debug logs a debug message
func (l *Logger) Debug(msg string, fields ...map[string]interface{}) {
l.log(LevelDebug, msg, nil, fields...)
}
// Info logs an info message
func (l *Logger) Info(msg string, fields ...map[string]interface{}) {
l.log(LevelInfo, msg, nil, fields...)
}
// Warn logs a warning message
func (l *Logger) Warn(msg string, fields ...map[string]interface{}) {
l.log(LevelWarn, msg, nil, fields...)
}
// Error logs an error message
func (l *Logger) Error(msg string, err error, fields ...map[string]interface{}) {
l.log(LevelError, msg, err, fields...)
}
// log writes a log entry
func (l *Logger) log(level Level, msg string, err error, fields ...map[string]interface{}) {
if level < l.level {
return
}
l.mu.Lock()
defer l.mu.Unlock()
entry := LogEntry{
Timestamp: time.Now().Format(time.RFC3339),
Level: levelNames[level],
Message: msg,
}
if err != nil {
entry.Error = err.Error()
}
// Merge fields
if len(fields) > 0 {
entry.Fields = make(map[string]interface{})
for _, f := range fields {
for k, v := range f {
entry.Fields[k] = v
}
}
}
var output string
if l.jsonMode {
jsonData, jsonErr := json.Marshal(entry)
if jsonErr != nil {
// Fallback to text format if JSON fails
output = fmt.Sprintf("%s [%s] %s", entry.Timestamp, entry.Level, msg)
if err != nil {
output += fmt.Sprintf(" error=%v", err)
}
} else {
output = string(jsonData)
}
} else {
// Text format
output = fmt.Sprintf("%s [%s] %s", entry.Timestamp, entry.Level, msg)
if err != nil {
output += fmt.Sprintf(" error=%v", err)
}
if len(entry.Fields) > 0 {
for k, v := range entry.Fields {
output += fmt.Sprintf(" %s=%v", k, v)
}
}
}
fmt.Fprintln(l.output, output)
}
// WithFields returns a logger with additional fields
func (l *Logger) WithFields(fields map[string]interface{}) *Logger {
return &Logger{
level: l.level,
output: l.output,
jsonMode: l.jsonMode,
prefix: l.prefix,
}
}
// ParseLevel parses a log level string
func ParseLevel(s string) Level {
switch s {
case "DEBUG", "debug":
return LevelDebug
case "INFO", "info":
return LevelInfo
case "WARN", "warn", "WARNING", "warning":
return LevelWarn
case "ERROR", "error":
return LevelError
default:
return LevelInfo
}
}
// Default logger instance
var defaultLogger *Logger
func init() {
levelStr := os.Getenv("ATLAS_LOG_LEVEL")
level := ParseLevel(levelStr)
jsonMode := os.Getenv("ATLAS_LOG_FORMAT") == "json"
defaultLogger = New(level, os.Stdout, jsonMode)
}
// Debug logs using default logger
func Debug(msg string, fields ...map[string]interface{}) {
defaultLogger.Debug(msg, fields...)
}
// Info logs using default logger
func Info(msg string, fields ...map[string]interface{}) {
defaultLogger.Info(msg, fields...)
}
// Warn logs using default logger
func Warn(msg string, fields ...map[string]interface{}) {
defaultLogger.Warn(msg, fields...)
}
// Error logs using default logger
func Error(msg string, err error, fields ...map[string]interface{}) {
defaultLogger.Error(msg, err, fields...)
}
// SetLevel sets the default logger level
func SetLevel(level Level) {
defaultLogger.SetLevel(level)
}
// GetLogger returns the default logger
func GetLogger() *Logger {
return defaultLogger
}