Skip to content

Commit

Permalink
add metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
denpeshkov committed Feb 18, 2024
1 parent ddf2a6c commit d1d5941
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 2 deletions.
11 changes: 11 additions & 0 deletions cmd/greenlight/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
package main

import (
"expvar"
"flag"
"fmt"
"log/slog"
"os"
"os/signal"
"runtime"
"syscall"
"time"

Expand Down Expand Up @@ -90,6 +92,15 @@ func run(cfg *Config, logger *slog.Logger) error {
http.WithLimiterBurst(cfg.limiter.burst),
)

// Metrics
expvar.NewString("version").Set(greenlight.AppVersion)
expvar.Publish("goroutines", expvar.Func(func() any {
return runtime.NumGoroutine()
}))
expvar.Publish("database", expvar.Func(func() any {
return db.Stats()
}))

// Application graceful shutdown
shutdownErr := make(chan error)
go func() {
Expand Down
3 changes: 3 additions & 0 deletions internal/greenlight/greenlight.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package greenlight

const AppVersion = "1.0.0"
10 changes: 9 additions & 1 deletion internal/http/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
func (s *Server) Error(w http.ResponseWriter, r *http.Request, e error) {
code := ErrorStatusCode(e)
if code == http.StatusInternalServerError {
s.logger.Error("Error processing request", "method", r.Method, "path", r.URL.Path, "error", e.Error())
s.LogError(w, r, "Error processing request", e)
}
errResp := ErrorBody(e)

Expand All @@ -22,6 +22,14 @@ func (s *Server) Error(w http.ResponseWriter, r *http.Request, e error) {
}
}

func (s *Server) Log(w http.ResponseWriter, r *http.Request, msg string) {
s.logger.Error(msg, "method", r.Method, "path", r.URL.Path)
}

func (s *Server) LogError(w http.ResponseWriter, r *http.Request, msg string, e error) {
s.logger.Error(e.Error(), "method", r.Method, "path", r.URL.Path, "error", e.Error())
}

func ErrorStatusCode(err error) int {
switch {
case errors.As(err, new(*greenlight.NotFoundError)):
Expand Down
5 changes: 4 additions & 1 deletion internal/http/healtcheck.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
package http

import (
"expvar"
"net/http"

"github.com/denpeshkov/greenlight/internal/greenlight"
"github.com/denpeshkov/greenlight/internal/multierr"
)

func (s *Server) registerHealthCheckHandlers() {
s.router.Handle("GET /v1/healthcheck", s.handlerFunc(s.handleHealthCheck))
s.router.Handle("GET /v1/metrics", expvar.Handler())
}

// handleHealthCheck handles requests to get application information (status).
func (s *Server) handleHealthCheck(w http.ResponseWriter, r *http.Request) (err error) {
defer multierr.Wrap(&err, "http.Server.handleHealthCheck")

info := HealthInfo{"1.0", "UP"}
info := HealthInfo{greenlight.AppVersion, "up"}
w.Header().Set("Content-Type", "application/json")
if err := s.sendResponse(w, r, http.StatusOK, info, nil); err != nil {
return err
Expand Down
23 changes: 23 additions & 0 deletions internal/http/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package http

import (
"encoding/json"
"expvar"
"fmt"
"net"
"net/http"
Expand Down Expand Up @@ -121,6 +122,7 @@ func (s *Server) rateLimit(h http.Handler) http.Handler {
clim.lastSeen = time.Now()
if !clim.lim.Allow() {
s.Error(w, r, greenlight.NewRateLimitError("Rate limit exceeded."))
s.Log(w, r, "Rate limit exceeded.")
return
}

Expand Down Expand Up @@ -157,3 +159,24 @@ func (s *Server) authenticate(h http.Handler) http.Handler {
h.ServeHTTP(w, r)
})
}

func (s *Server) metrics(next http.Handler) http.Handler {
var (
totalRequestsReceived = expvar.NewInt("total_requests_received")
totalResponsesSent = expvar.NewInt("total_responses_sent")
totalProcessingTimeMicroseconds = expvar.NewInt("total_processing_time_μs")
)
// The following code will be run for every request...
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Record the time that we started to process the request.
start := time.Now()
// Use the Add() method to increment the number of requests received by 1.
totalRequestsReceived.Add(1)
// Call the next handler in the chain.
next.ServeHTTP(w, r)
// On the way back up the middleware chain, increment the number of responses // sent by 1.
totalResponsesSent.Add(1)
// Calculate the number of microseconds since we began to process the request, // then increment the total processing time by this amount.
duration := time.Since(start).Microseconds()
totalProcessingTimeMicroseconds.Add(duration)
})
}
1 change: 1 addition & 0 deletions internal/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func (s *Server) Open() (err error) {
h = s.notFound(h)
h = s.rateLimit(h)
h = s.recoverPanic(h)
h = s.metrics(h)
s.server.Handler = h

s.server.ErrorLog = slog.NewLogLogger(s.logger.Handler(), slog.LevelError)
Expand Down
4 changes: 4 additions & 0 deletions internal/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ func (db *DB) Close() (err error) {
return nil
}

func (db *DB) Stats() sql.DBStats {
return db.db.Stats()
}

// newLogger returns a database logger.
func newLogger() *slog.Logger {
opts := slog.HandlerOptions{Level: slog.LevelDebug}
Expand Down

0 comments on commit d1d5941

Please sign in to comment.