159 lines
2.9 KiB
Go
159 lines
2.9 KiB
Go
// 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))
|
|
}
|