From b8fff8f7d57bf638379d4bfb71f71af89818046f Mon Sep 17 00:00:00 2001 From: lbenedar Date: Thu, 19 Mar 2026 13:44:50 +0300 Subject: [PATCH] ch10.1 --- cmd/api/errors.go | 5 +- cmd/api/healthcheck.go | 1 - cmd/api/main.go | 17 ++++--- internal/jsonlog/jsonlog.go | 97 +++++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 internal/jsonlog/jsonlog.go diff --git a/cmd/api/errors.go b/cmd/api/errors.go index ecd9c30..9a87e52 100644 --- a/cmd/api/errors.go +++ b/cmd/api/errors.go @@ -6,7 +6,10 @@ import ( ) func (app *application) logError(r *http.Request, err error) { - app.logger.Print(err) + app.logger.PrintError(err, map[string]string{ + "request_method": r.Method, + "request_url": r.URL.String(), + }) } func (app *application) errorResponse(w http.ResponseWriter, r *http.Request, status int, message any) { diff --git a/cmd/api/healthcheck.go b/cmd/api/healthcheck.go index 052cf41..2e7914b 100644 --- a/cmd/api/healthcheck.go +++ b/cmd/api/healthcheck.go @@ -14,7 +14,6 @@ func (app *application) healthcheckHandler(w http.ResponseWriter, r *http.Reques } err := app.writeJSON(w, http.StatusOK, env, nil) if err != nil { - app.logger.Print(err) app.serverErrorResponse(w, r, err) } } diff --git a/cmd/api/main.go b/cmd/api/main.go index 6302119..a6a3613 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -11,6 +11,7 @@ import ( "time" "gitea.local.lab/Lbenedar/greenlight/internal/data" + "gitea.local.lab/Lbenedar/greenlight/internal/jsonlog" _ "github.com/lib/pq" ) @@ -18,7 +19,7 @@ const version = "1.0.0" type application struct { config config - logger *log.Logger + logger *jsonlog.Logger models data.Models } @@ -70,15 +71,15 @@ func main() { flag.StringVar(&cfg.db.maxIdleTime, "db-max-idle-time", "15m", "PostgreSQL max connection idle time") flag.Parse() - logger := log.New(os.Stdout, "", log.Ldate|log.Ltime) + logger := jsonlog.New(os.Stdout, jsonlog.LevelInfo) db, err := openDB(cfg) if err != nil { - logger.Fatal(err) + logger.PrintFatal(err, nil) } defer db.Close() - logger.Printf("database connection pool established") + logger.PrintInfo("database connection pool established", nil) app := &application{ config: cfg, @@ -89,11 +90,15 @@ func main() { srv := &http.Server{ Addr: fmt.Sprintf(":%d", app.config.port), Handler: app.routes(), + ErrorLog: log.New(logger, "", 0), IdleTimeout: time.Minute, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, } - logger.Printf("starting %s server on %s", cfg.env, srv.Addr) + logger.PrintInfo("starting server", map[string]string{ + "addr": srv.Addr, + "env": cfg.env, + }) err = srv.ListenAndServe() - logger.Fatal(err) + logger.PrintFatal(err, nil) } diff --git a/internal/jsonlog/jsonlog.go b/internal/jsonlog/jsonlog.go new file mode 100644 index 0000000..b33baed --- /dev/null +++ b/internal/jsonlog/jsonlog.go @@ -0,0 +1,97 @@ +package jsonlog + +import ( + "encoding/json" + "io" + "os" + "runtime/debug" + "sync" + "time" +) + +type Level int8 + +const ( + LevelInfo Level = iota + LevelError + LevelFatal + LevelOff +) + +func (l Level) String() string { + switch l { + case LevelInfo: + return "INFO" + case LevelError: + return "ERROR" + case LevelFatal: + return "FATAL" + default: + return "" + } +} + +type Logger struct { + out io.Writer + minLevel Level + mu sync.Mutex +} + +func New(out io.Writer, minLevel Level) *Logger { + return &Logger{ + out: out, + minLevel: minLevel, + } +} + +func (l *Logger) PrintInfo(message string, properties map[string]string) { + l.print(LevelInfo, message, properties) +} + +func (l *Logger) PrintError(err error, properties map[string]string) { + l.print(LevelError, err.Error(), properties) +} + +func (l *Logger) PrintFatal(err error, properties map[string]string) { + l.print(LevelFatal, err.Error(), properties) + os.Exit(1) +} + +func (l *Logger) print(level Level, message string, properties map[string]string) (int, error) { + if level < l.minLevel { + return 0, nil + } + + aux := struct { + Level string `json:"level"` + Time string `json:"time"` + Message string `json:"message"` + Properties map[string]string `json:"properties,omitempty"` + Trace string `json:"trace,omitempty"` + }{ + Level: level.String(), + Time: time.Now().UTC().Format(time.RFC3339), + Message: message, + Properties: properties, + } + + if level >= LevelError { + aux.Trace = string(debug.Stack()) + } + + var line []byte + + line, err := json.Marshal(aux) + if err != nil { + line = []byte(LevelError.String() + ": unable to marshal log message: " + err.Error()) + } + + l.mu.Lock() + defer l.mu.Unlock() + + return l.out.Write(append(line, '\n')) +} + +func (l *Logger) Write(message []byte) (n int, err error) { + return l.print(LevelError, string(message), nil) +}