Commit 0361002

bryfry <bryon@fryer.io>
2025-07-24 09:35:43
Task 1.12: Implement structured logging system
- Replace single logger with separate info/error loggers - Add INFO logger to stdout with timestamps - Add ERROR logger to stderr with timestamps and file locations - Update all handlers to use structured error logging - Add startup configuration logging for visibility See: docs/todo/task_1.12.md
1 parent 648a15c
Changed files (3)
cmd/web/handlers.go
@@ -25,8 +25,11 @@ type config struct {
 // application holds the application-wide dependencies and configuration
 // This demonstrates a custom handler type that can hold state and dependencies
 type application struct {
-	config      config
-	logger      *log.Logger
+	config config
+	logger struct {
+		info  *log.Logger
+		error *log.Logger
+	}
 	serviceName string
 }
 
@@ -49,7 +52,7 @@ func (th *TemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		th.app.config.htmlDir+"/pages/"+th.templateName,
 	)
 	if err != nil {
-		th.app.logger.Println(err.Error())
+		th.app.logger.error.Println(err.Error())
 		http.Error(w, "Internal Server Error", 500)
 		return
 	}
@@ -64,7 +67,7 @@ func (th *TemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	// Execute template
 	err = ts.ExecuteTemplate(w, "base", data)
 	if err != nil {
-		th.app.logger.Println(err.Error())
+		th.app.logger.error.Println(err.Error())
 		http.Error(w, "Internal Server Error", 500)
 	}
 }
@@ -81,7 +84,7 @@ func (app *application) home(w http.ResponseWriter, r *http.Request) {
 		app.config.htmlDir+"/pages/home.tmpl",
 	)
 	if err != nil {
-		app.logger.Println(err.Error())
+		app.logger.error.Println(err.Error())
 		http.Error(w, "Internal Server Error", 500)
 		return
 	}
@@ -96,7 +99,7 @@ func (app *application) home(w http.ResponseWriter, r *http.Request) {
 	// Execute template
 	err = ts.ExecuteTemplate(w, "base", data)
 	if err != nil {
-		app.logger.Println(err.Error())
+		app.logger.error.Println(err.Error())
 		http.Error(w, "Internal Server Error", 500)
 	}
 }
@@ -115,7 +118,7 @@ func (app *application) processSubmit(w http.ResponseWriter, r *http.Request) {
 		app.config.htmlDir+"/pages/complete.tmpl",
 	)
 	if err != nil {
-		app.logger.Println(err.Error())
+		app.logger.error.Println(err.Error())
 		http.Error(w, "Internal Server Error", 500)
 		return
 	}
@@ -130,7 +133,7 @@ func (app *application) processSubmit(w http.ResponseWriter, r *http.Request) {
 	// Execute template
 	err = ts.ExecuteTemplate(w, "base", data)
 	if err != nil {
-		app.logger.Println(err.Error())
+		app.logger.error.Println(err.Error())
 		http.Error(w, "Internal Server Error", 500)
 	}
 }
cmd/web/main.go
@@ -35,11 +35,25 @@ func main() {
 		cfg.htmlDir = envHtmlDir
 	}
 
+	// Create loggers for different types of messages
+	infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
+	errorLog := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)
+
+	// Log startup configuration
+	infoLog.Printf("Configuration loaded: port=%d, env=%s, staticDir=%s, htmlDir=%s", 
+		cfg.port, cfg.env, cfg.staticDir, cfg.htmlDir)
+
 	// Create an instance of the application struct, containing the application-wide
 	// dependencies and configuration settings.
 	app := &application{
-		config:      cfg,
-		logger:      log.New(log.Writer(), "", log.LstdFlags),
+		config: cfg,
+		logger: struct {
+			info  *log.Logger
+			error *log.Logger
+		}{
+			info:  infoLog,
+			error: errorLog,
+		},
 		serviceName: "buylater.email",
 	}
 
@@ -85,7 +99,7 @@ func main() {
 	})
 
 	// Print a log message to indicate the server is starting.
-	app.logger.Printf("Starting server on http://localhost:%d in %s mode", app.config.port, app.config.env)
+	app.logger.info.Printf("Starting server on http://localhost:%d in %s mode", app.config.port, app.config.env)
 
 	// Start the web server on the configured port. If ListenAndServe returns an error
 	// we use log.Fatal() to log the error and terminate the program.
docs/project_plan.md
@@ -19,7 +19,7 @@ Phase 1 lays the groundwork by methodically working through the Let's Go book, c
 | 1.9  | Serving Static Files                    | Completed | Small  | 2.9     | [task_1.9.md](todo/task_1.9.md) |
 | 1.10 | The HTTP Handler Interface              | Completed | Medium | 2.10    | [task_1.10.md](todo/task_1.10.md) |
 | 1.11 | Managing Configuration Settings         | Completed | Medium | 3.1     | [task_1.11.md](todo/task_1.11.md) |
-| 1.12 | Structured Logging                      | Pending   | Medium | 3.2     | [task_1.12.md](todo/task_1.12.md) |
+| 1.12 | Structured Logging                      | Completed | Medium | 3.2     | [task_1.12.md](todo/task_1.12.md) |
 | 1.13 | Dependency Injection                    | Pending   | Medium | 3.3     | [task_1.13.md](todo/task_1.13.md) |
 | 1.14 | Centralized Error Handling             | Pending   | Medium | 3.4     | [task_1.14.md](todo/task_1.14.md) |
 | 1.15 | Isolating the Application Routes        | Pending   | Small  | 3.5     | [task_1.15.md](todo/task_1.15.md) |