// backend/log.go package main import ( "encoding/json" "errors" "fmt" "log" "os" "time" ) type LogLevel string const ( LogLevelInfo LogLevel = "info" LogLevelWarning LogLevel = "warning" LogLevelError LogLevel = "error" ) type LogFields map[string]any type AppError struct { Message string Err error Fields LogFields } var appLogger = log.New(os.Stdout, "", 0) func (e *AppError) Error() string { if e == nil { return "" } if e.Err == nil { return e.Message } if e.Message == "" { return e.Err.Error() } return e.Message + ": " + e.Err.Error() } func (e *AppError) Unwrap() error { if e == nil { return nil } return e.Err } func newErrorf(format string, args ...any) error { return fmt.Errorf(format, args...) } func newAppError(message string, err error, fields LogFields) error { return &AppError{ Message: message, Err: err, Fields: fields, } } func newAppErrorf(message string, fields LogFields, format string, args ...any) error { return newAppError( message, fmt.Errorf(format, args...), fields, ) } func formatText(format string, args ...any) string { return fmt.Sprintf(format, args...) } func logInfo(message string, fields LogFields) { writeAppLog(LogLevelInfo, message, nil, fields) } func logInfof(format string, args ...any) { writeAppLog(LogLevelInfo, fmt.Sprintf(format, args...), nil, nil) } func logWarning(message string, fields LogFields) { writeAppLog(LogLevelWarning, message, nil, fields) } func logWarningf(format string, args ...any) { writeAppLog(LogLevelWarning, fmt.Sprintf(format, args...), nil, nil) } func logError(message string, err error, fields LogFields) { writeAppLog(LogLevelError, message, err, fields) } func logErrorf(err error, format string, args ...any) { writeAppLog(LogLevelError, fmt.Sprintf(format, args...), err, nil) } func appErrorFields(err error) LogFields { var appErr *AppError if errors.As(err, &appErr) && appErr.Fields != nil { return appErr.Fields } return nil } func mergeLogFields(base LogFields, extra LogFields) LogFields { if len(base) == 0 && len(extra) == 0 { return nil } merged := LogFields{} for key, value := range base { merged[key] = value } for key, value := range extra { merged[key] = value } return merged } func writeAppLog(level LogLevel, message string, err error, fields LogFields) { payload := map[string]any{ "timestamp": time.Now().UTC().Format(time.RFC3339Nano), "level": level, "message": message, } if err != nil { payload["error"] = err.Error() } if len(fields) > 0 { payload["fields"] = fields } data, marshalErr := json.Marshal(payload) if marshalErr != nil { appLogger.Printf( `{"timestamp":"%s","level":"error","message":"could not marshal log payload","error":%q}`, time.Now().UTC().Format(time.RFC3339Nano), marshalErr.Error(), ) return } appLogger.Println(string(data)) }