From 85e6dad1c53c8ac18534c8d36a5e24a44d5410e0 Mon Sep 17 00:00:00 2001 From: Julien Semaan Date: Mon, 13 Apr 2026 23:45:06 +0000 Subject: [PATCH 1/3] chore(api): standardize error log prefixes --- api/cmd/api/main.go | 2 +- api/internal/app/run.go | 2 +- api/internal/contact/service.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/cmd/api/main.go b/api/cmd/api/main.go index 8e6b4f7..edbd624 100644 --- a/api/cmd/api/main.go +++ b/api/cmd/api/main.go @@ -8,6 +8,6 @@ import ( func main() { if err := app.Run(app.LoadConfigFromEnv()); err != nil { - log.Fatal(err) + log.Fatalf("ERROR: %v", err) } } diff --git a/api/internal/app/run.go b/api/internal/app/run.go index d5efdbf..4414529 100644 --- a/api/internal/app/run.go +++ b/api/internal/app/run.go @@ -22,7 +22,7 @@ func Run(cfg Config) error { statsConn, err := statsd.New(statsd.Address(cfg.StatsdAddress)) if err != nil { - log.Printf("warning: statsd initialization failed: %v", err) + log.Printf("ERROR: statsd initialization failed: %v", err) } if statsConn != nil { defer statsConn.Close() diff --git a/api/internal/contact/service.go b/api/internal/contact/service.go index f340afd..6d83798 100644 --- a/api/internal/contact/service.go +++ b/api/internal/contact/service.go @@ -58,11 +58,11 @@ func (s *Service) BeforeInsert(_ context.Context, items []*resource.Item) error for _, item := range items { msg, buildErr := buildMessage(recipients, item) if buildErr != nil { - log.Printf("Unable to build contact request email: %v", buildErr) + log.Printf("ERROR: Unable to build contact request email: %v", buildErr) return errUnableToSendEmail } if sendErr := s.sendEmail(recipients, msg); sendErr != nil { - log.Printf("Unable to send contact request email: %v", sendErr) + log.Printf("ERROR: Unable to send contact request email: %v", sendErr) return errUnableToSendEmail } } @@ -72,7 +72,7 @@ func (s *Service) BeforeInsert(_ context.Context, items []*resource.Item) error func (s *Service) AfterInsert(_ context.Context, items []*resource.Item, err *error) { if err != nil && *err != nil { - log.Println("Not sending contact request email because there was an error saving the contact request") + log.Println("ERROR: Not sending contact request email because there was an error saving the contact request") return } From 674acca63eda7519f87a3eb78b68ada765af6678 Mon Sep 17 00:00:00 2001 From: Julien Semaan Date: Mon, 13 Apr 2026 23:54:48 +0000 Subject: [PATCH 2/3] refactor(api): centralize error logging --- api/cmd/api/main.go | 5 ++--- api/internal/app/email.go | 5 +++-- api/internal/app/run.go | 3 ++- api/internal/contact/service.go | 8 ++++---- api/internal/logging/logging.go | 16 ++++++++++++++++ 5 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 api/internal/logging/logging.go diff --git a/api/cmd/api/main.go b/api/cmd/api/main.go index edbd624..a6ef107 100644 --- a/api/cmd/api/main.go +++ b/api/cmd/api/main.go @@ -1,13 +1,12 @@ package main import ( - "log" - "github.com/julsemaan/anyfile-notepad/api/internal/app" + "github.com/julsemaan/anyfile-notepad/api/internal/logging" ) func main() { if err := app.Run(app.LoadConfigFromEnv()); err != nil { - log.Fatalf("ERROR: %v", err) + logging.Fatalf("%v", err) } } diff --git a/api/internal/app/email.go b/api/internal/app/email.go index 6f34dbd..dd5ec83 100644 --- a/api/internal/app/email.go +++ b/api/internal/app/email.go @@ -2,12 +2,13 @@ package app import ( "crypto/tls" - "fmt" "net" "net/smtp" "os" "strconv" "strings" + + "github.com/julsemaan/anyfile-notepad/api/internal/logging" ) var smtpSendMail = smtp.SendMail @@ -37,7 +38,7 @@ func sendEmailWithOptionalTLS(to []string, msg []byte) error { err = smtpSendMail(addr, auth, from, to, msg) } if err != nil { - fmt.Println("ERROR: Unable to send email:", err) + logging.Error("Unable to send email:", err) return err } diff --git a/api/internal/app/run.go b/api/internal/app/run.go index 4414529..6403817 100644 --- a/api/internal/app/run.go +++ b/api/internal/app/run.go @@ -7,6 +7,7 @@ import ( "github.com/julsemaan/anyfile-notepad/api/internal/contact" "github.com/julsemaan/anyfile-notepad/api/internal/httpapi" + "github.com/julsemaan/anyfile-notepad/api/internal/logging" "github.com/julsemaan/anyfile-notepad/api/internal/resources" "github.com/julsemaan/anyfile-notepad/api/internal/stats" cache "github.com/patrickmn/go-cache" @@ -22,7 +23,7 @@ func Run(cfg Config) error { statsConn, err := statsd.New(statsd.Address(cfg.StatsdAddress)) if err != nil { - log.Printf("ERROR: statsd initialization failed: %v", err) + logging.Errorf("statsd initialization failed: %v", err) } if statsConn != nil { defer statsConn.Close() diff --git a/api/internal/contact/service.go b/api/internal/contact/service.go index 6d83798..2a1639b 100644 --- a/api/internal/contact/service.go +++ b/api/internal/contact/service.go @@ -5,10 +5,10 @@ import ( "context" "errors" "fmt" - "log" "strings" "text/template" + "github.com/julsemaan/anyfile-notepad/api/internal/logging" "github.com/rs/rest-layer/resource" ) @@ -58,11 +58,11 @@ func (s *Service) BeforeInsert(_ context.Context, items []*resource.Item) error for _, item := range items { msg, buildErr := buildMessage(recipients, item) if buildErr != nil { - log.Printf("ERROR: Unable to build contact request email: %v", buildErr) + logging.Errorf("Unable to build contact request email: %v", buildErr) return errUnableToSendEmail } if sendErr := s.sendEmail(recipients, msg); sendErr != nil { - log.Printf("ERROR: Unable to send contact request email: %v", sendErr) + logging.Errorf("Unable to send contact request email: %v", sendErr) return errUnableToSendEmail } } @@ -72,7 +72,7 @@ func (s *Service) BeforeInsert(_ context.Context, items []*resource.Item) error func (s *Service) AfterInsert(_ context.Context, items []*resource.Item, err *error) { if err != nil && *err != nil { - log.Println("ERROR: Not sending contact request email because there was an error saving the contact request") + logging.Error("Not sending contact request email because there was an error saving the contact request") return } diff --git a/api/internal/logging/logging.go b/api/internal/logging/logging.go new file mode 100644 index 0000000..9232280 --- /dev/null +++ b/api/internal/logging/logging.go @@ -0,0 +1,16 @@ +package logging + +import "log" + +func Error(args ...interface{}) { + args = append([]interface{}{"ERROR:"}, args...) + log.Println(args...) +} + +func Errorf(format string, args ...interface{}) { + log.Printf("ERROR: "+format, args...) +} + +func Fatalf(format string, args ...interface{}) { + log.Fatalf("ERROR: "+format, args...) +} From 5988fdb274fb8d24ef5ec57f81493728f3847150 Mon Sep 17 00:00:00 2001 From: Julien Semaan Date: Tue, 14 Apr 2026 00:03:55 +0000 Subject: [PATCH 3/3] fix(logging): standardize error output and fatal exit behavior --- api/internal/logging/logging.go | 27 ++++++++-- api/internal/logging/logging_test.go | 81 ++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 api/internal/logging/logging_test.go diff --git a/api/internal/logging/logging.go b/api/internal/logging/logging.go index 9232280..12528ac 100644 --- a/api/internal/logging/logging.go +++ b/api/internal/logging/logging.go @@ -1,16 +1,33 @@ package logging -import "log" +import ( + "fmt" + "log" + "os" + "strings" +) + +const errorPrefix = "ERROR:" + +func output(message string) { + _ = log.Default().Output(3, message) +} func Error(args ...interface{}) { - args = append([]interface{}{"ERROR:"}, args...) - log.Println(args...) + msg := strings.TrimSuffix(fmt.Sprintln(args...), "\n") + if msg == "" { + output(errorPrefix) + return + } + + output(errorPrefix + " " + msg) } func Errorf(format string, args ...interface{}) { - log.Printf("ERROR: "+format, args...) + output(errorPrefix + " " + fmt.Sprintf(format, args...)) } func Fatalf(format string, args ...interface{}) { - log.Fatalf("ERROR: "+format, args...) + output(errorPrefix + " " + fmt.Sprintf(format, args...)) + os.Exit(1) } diff --git a/api/internal/logging/logging_test.go b/api/internal/logging/logging_test.go new file mode 100644 index 0000000..df2cbd9 --- /dev/null +++ b/api/internal/logging/logging_test.go @@ -0,0 +1,81 @@ +package logging + +import ( + "bytes" + "errors" + "log" + "os" + "os/exec" + "strings" + "testing" +) + +func captureLogOutput(t *testing.T) *bytes.Buffer { + t.Helper() + + buf := &bytes.Buffer{} + originalWriter := log.Writer() + originalFlags := log.Flags() + originalPrefix := log.Prefix() + + log.SetOutput(buf) + log.SetFlags(0) + log.SetPrefix("") + + t.Cleanup(func() { + log.SetOutput(originalWriter) + log.SetFlags(originalFlags) + log.SetPrefix(originalPrefix) + }) + + return buf +} + +func TestErrorAddsPrefix(t *testing.T) { + buf := captureLogOutput(t) + + Error("unable to send email", 42) + + if got := buf.String(); !strings.Contains(got, "ERROR: unable to send email 42") { + t.Fatalf("expected ERROR prefix in output, got %q", got) + } +} + +func TestErrorfAddsPrefix(t *testing.T) { + buf := captureLogOutput(t) + + Errorf("unable to send email %d", 42) + + if got := buf.String(); !strings.Contains(got, "ERROR: unable to send email 42") { + t.Fatalf("expected ERROR prefix in output, got %q", got) + } +} + +func TestFatalfAddsPrefixAndExits(t *testing.T) { + if os.Getenv("LOGGING_FATALF_TEST") == "1" { + log.SetOutput(os.Stderr) + log.SetFlags(0) + log.SetPrefix("") + Fatalf("unable to send email %d", 42) + return + } + + cmd := exec.Command(os.Args[0], "-test.run=TestFatalfAddsPrefixAndExits") + cmd.Env = append(os.Environ(), "LOGGING_FATALF_TEST=1") + + var stderr bytes.Buffer + cmd.Stderr = &stderr + + err := cmd.Run() + var exitErr *exec.ExitError + if !errors.As(err, &exitErr) { + t.Fatalf("expected process exit error, got %v", err) + } + if exitErr.ExitCode() != 1 { + t.Fatalf("expected exit code 1, got %d", exitErr.ExitCode()) + } + + if got := stderr.String(); !strings.Contains(got, "ERROR: unable to send email 42") { + t.Fatalf("expected ERROR prefix in fatal output, got %q", got) + } +}