diff --git a/api/cmd/api/main.go b/api/cmd/api/main.go index 8e6b4f7..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.Fatal(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 d5efdbf..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("warning: 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 f340afd..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("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("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("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..12528ac --- /dev/null +++ b/api/internal/logging/logging.go @@ -0,0 +1,33 @@ +package logging + +import ( + "fmt" + "log" + "os" + "strings" +) + +const errorPrefix = "ERROR:" + +func output(message string) { + _ = log.Default().Output(3, message) +} + +func Error(args ...interface{}) { + msg := strings.TrimSuffix(fmt.Sprintln(args...), "\n") + if msg == "" { + output(errorPrefix) + return + } + + output(errorPrefix + " " + msg) +} + +func Errorf(format string, args ...interface{}) { + output(errorPrefix + " " + fmt.Sprintf(format, args...)) +} + +func Fatalf(format string, args ...interface{}) { + 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) + } +}