Spaces:
Sleeping
Sleeping
| package logger | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "log" | |
| "os" | |
| "runtime" | |
| "strings" | |
| "sync" | |
| "time" | |
| ) | |
| type LogLevel int | |
| const ( | |
| DEBUG LogLevel = iota | |
| INFO | |
| WARN | |
| ERROR | |
| FATAL | |
| ) | |
| var ( | |
| logLevelNames = map[LogLevel]string{ | |
| DEBUG: "DEBUG", | |
| INFO: "INFO", | |
| WARN: "WARN", | |
| ERROR: "ERROR", | |
| FATAL: "FATAL", | |
| } | |
| currentLevel = INFO | |
| logger *Logger | |
| once sync.Once | |
| mu sync.RWMutex | |
| ) | |
| type Logger struct { | |
| file *os.File | |
| } | |
| type LogEntry struct { | |
| Level string `json:"level"` | |
| Timestamp string `json:"timestamp"` | |
| Component string `json:"component,omitempty"` | |
| Message string `json:"message"` | |
| Fields map[string]interface{} `json:"fields,omitempty"` | |
| Caller string `json:"caller,omitempty"` | |
| } | |
| func init() { | |
| once.Do(func() { | |
| logger = &Logger{} | |
| // Disable standard log timestamps as we handle formatting ourselves | |
| log.SetFlags(0) | |
| log.SetOutput(os.Stdout) | |
| }) | |
| } | |
| func SetLevel(level LogLevel) { | |
| mu.Lock() | |
| defer mu.Unlock() | |
| currentLevel = level | |
| } | |
| func GetLevel() LogLevel { | |
| mu.RLock() | |
| defer mu.RUnlock() | |
| return currentLevel | |
| } | |
| func EnableFileLogging(filePath string) error { | |
| mu.Lock() | |
| defer mu.Unlock() | |
| file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) | |
| if err != nil { | |
| return fmt.Errorf("failed to open log file: %w", err) | |
| } | |
| if logger.file != nil { | |
| logger.file.Close() | |
| } | |
| logger.file = file | |
| log.Println("File logging enabled:", filePath) | |
| return nil | |
| } | |
| func DisableFileLogging() { | |
| mu.Lock() | |
| defer mu.Unlock() | |
| if logger.file != nil { | |
| logger.file.Close() | |
| logger.file = nil | |
| log.Println("File logging disabled") | |
| } | |
| } | |
| func getWIBTime() string { | |
| // Try to load Asia/Jakarta location | |
| loc, err := time.LoadLocation("Asia/Jakarta") | |
| if err != nil { | |
| // Fallback to FixedZone (UTC+7) if zoneinfo is missing | |
| loc = time.FixedZone("WIB", 7*60*60) | |
| } | |
| return time.Now().In(loc).Format(time.RFC3339) | |
| } | |
| func logMessage(level LogLevel, component string, message string, fields map[string]interface{}) { | |
| if level < currentLevel { | |
| return | |
| } | |
| entry := LogEntry{ | |
| Level: logLevelNames[level], | |
| Timestamp: getWIBTime(), | |
| Component: component, | |
| Message: message, | |
| Fields: fields, | |
| } | |
| if pc, file, line, ok := runtime.Caller(2); ok { | |
| fn := runtime.FuncForPC(pc) | |
| if fn != nil { | |
| entry.Caller = fmt.Sprintf("%s:%d (%s)", file, line, fn.Name()) | |
| } | |
| } | |
| if logger.file != nil { | |
| jsonData, err := json.Marshal(entry) | |
| if err == nil { | |
| logger.file.WriteString(string(jsonData) + "\n") | |
| } | |
| } | |
| var fieldStr string | |
| if len(fields) > 0 { | |
| fieldStr = " " + formatFields(fields) | |
| } | |
| logLine := fmt.Sprintf("[%s] [%s]%s %s%s", | |
| entry.Timestamp, | |
| logLevelNames[level], | |
| formatComponent(component), | |
| message, | |
| fieldStr, | |
| ) | |
| log.Println(logLine) | |
| if level == FATAL { | |
| os.Exit(1) | |
| } | |
| } | |
| func formatComponent(component string) string { | |
| if component == "" { | |
| return "" | |
| } | |
| return fmt.Sprintf(" %s:", component) | |
| } | |
| func formatFields(fields map[string]interface{}) string { | |
| var parts []string | |
| for k, v := range fields { | |
| parts = append(parts, fmt.Sprintf("%s=%v", k, v)) | |
| } | |
| return fmt.Sprintf("{%s}", strings.Join(parts, ", ")) | |
| } | |
| func Debug(message string) { | |
| logMessage(DEBUG, "", message, nil) | |
| } | |
| func DebugC(component string, message string) { | |
| logMessage(DEBUG, component, message, nil) | |
| } | |
| func DebugF(message string, fields map[string]interface{}) { | |
| logMessage(DEBUG, "", message, fields) | |
| } | |
| func DebugCF(component string, message string, fields map[string]interface{}) { | |
| logMessage(DEBUG, component, message, fields) | |
| } | |
| func Info(message string) { | |
| logMessage(INFO, "", message, nil) | |
| } | |
| func InfoC(component string, message string) { | |
| logMessage(INFO, component, message, nil) | |
| } | |
| func InfoF(message string, fields map[string]interface{}) { | |
| logMessage(INFO, "", message, fields) | |
| } | |
| func InfoCF(component string, message string, fields map[string]interface{}) { | |
| logMessage(INFO, component, message, fields) | |
| } | |
| func Warn(message string) { | |
| logMessage(WARN, "", message, nil) | |
| } | |
| func WarnC(component string, message string) { | |
| logMessage(WARN, component, message, nil) | |
| } | |
| func WarnF(message string, fields map[string]interface{}) { | |
| logMessage(WARN, "", message, fields) | |
| } | |
| func WarnCF(component string, message string, fields map[string]interface{}) { | |
| logMessage(WARN, component, message, fields) | |
| } | |
| func Error(message string) { | |
| logMessage(ERROR, "", message, nil) | |
| } | |
| func ErrorC(component string, message string) { | |
| logMessage(ERROR, component, message, nil) | |
| } | |
| func ErrorF(message string, fields map[string]interface{}) { | |
| logMessage(ERROR, "", message, fields) | |
| } | |
| func ErrorCF(component string, message string, fields map[string]interface{}) { | |
| logMessage(ERROR, component, message, fields) | |
| } | |
| func Fatal(message string) { | |
| logMessage(FATAL, "", message, nil) | |
| } | |
| func FatalC(component string, message string) { | |
| logMessage(FATAL, component, message, nil) | |
| } | |
| func FatalF(message string, fields map[string]interface{}) { | |
| logMessage(FATAL, "", message, fields) | |
| } | |
| func FatalCF(component string, message string, fields map[string]interface{}) { | |
| logMessage(FATAL, component, message, fields) | |
| } | |