Skip to content

Commit 9fd64b6

Browse files
committed
chi url router + xlog structured logging
1 parent ee5877d commit 9fd64b6

File tree

4 files changed

+140
-71
lines changed

4 files changed

+140
-71
lines changed

dashboard/js/alerts.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function alertSorter(a, b) {
2727

2828
var loadAlerts = function() {
2929
var request = new XMLHttpRequest();
30-
request.open("GET", "/api/alerts/" + prefix, true);
30+
request.open("GET", "/api/alerts" + prefix, true);
3131
request.onload = function() {
3232
if (request.responseText !== prevData) {
3333
var source = document.getElementById("alert-template").innerHTML;
@@ -93,4 +93,4 @@ function deleteAlert(element) {
9393
}
9494
}
9595
request.send();
96-
}
96+
}

db.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type store interface {
1616
getAlert(id string) []byte
1717
deleteAlert(id string) error
1818
backup(w http.ResponseWriter)
19-
getAlertsByPrefix(prefix string) ([]byte, error)
19+
getAlertsByPrefix(prefix string) ([]byte, int, error)
2020
}
2121

2222
type boltStore struct {
@@ -95,7 +95,7 @@ func (b *boltStore) deleteAlert(id string) error {
9595
return err
9696
}
9797

98-
func (b *boltStore) getAlertsByPrefix(prefix string) ([]byte, error) {
98+
func (b *boltStore) getAlertsByPrefix(prefix string) ([]byte, int, error) {
9999
alerts := make([]alertData, 0, 0)
100100
var alert alertData
101101

@@ -114,7 +114,7 @@ func (b *boltStore) getAlertsByPrefix(prefix string) ([]byte, error) {
114114
})
115115

116116
data, _ := json.Marshal(alerts)
117-
return data, err
117+
return data, len(alerts), err
118118
}
119119

120120
func (b *boltStore) Close() {

handler.go

+96-55
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,127 @@
11
package main
22

33
import (
4+
"context"
45
"encoding/base64"
56
"encoding/json"
7+
"fmt"
68
"net/http"
7-
"os"
9+
"net/http/httptest"
810
"time"
911

10-
"github.com/gorilla/handlers"
11-
"github.com/gorilla/mux"
12+
"github.com/pressly/chi"
13+
"github.com/rs/xlog"
1214
)
1315

14-
// some middleware handlers
15-
func timeoutHandler(h http.Handler) http.Handler {
16-
return http.TimeoutHandler(h, 10*time.Second, "timed out")
17-
}
16+
func logHandler(next http.Handler) http.Handler {
17+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
18+
rec := httptest.NewRecorder()
19+
t1 := time.Now()
20+
next.ServeHTTP(rec, r)
21+
22+
l := xlog.FromRequest(r)
23+
for k, v := range rec.Header() {
24+
w.Header()[k] = v
25+
}
26+
w.WriteHeader(rec.Code)
27+
w.Write(rec.Body.Bytes())
1828

19-
func loggingHandler(h http.Handler) http.Handler {
20-
return handlers.CombinedLoggingHandler(os.Stdout, h)
29+
t2 := time.Now()
30+
l.Info(xlog.F{
31+
"duration": t2.Sub(t1),
32+
"status": rec.Code,
33+
"size": rec.Body.Len(),
34+
})
35+
})
2136
}
2237

23-
// the real handlers
24-
func alertHandler(db store) func(w http.ResponseWriter, r *http.Request) {
25-
return func(w http.ResponseWriter, r *http.Request) {
26-
vars := mux.Vars(r)
27-
alertID, err := base64.URLEncoding.DecodeString(vars["alertID"])
38+
func alertCtx(next http.Handler) http.Handler {
39+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
40+
alertID, err := base64.URLEncoding.DecodeString(chi.URLParam(r, "alertID"))
2841
if err != nil {
29-
if err != nil {
30-
http.Error(w, err.Error(), http.StatusInternalServerError)
31-
return
32-
}
42+
http.Error(w, "", http.StatusInternalServerError)
43+
return
3344
}
34-
switch r.Method {
35-
case "GET":
36-
w.Header().Set("Content-Type", "application/json; charset=utf-8")
37-
w.Write(db.getAlert(string(alertID)))
45+
ctx := context.WithValue(r.Context(), "alertID", string(alertID))
46+
next.ServeHTTP(w, r.WithContext(ctx))
47+
})
48+
}
49+
50+
func getAlert(db store) func(w http.ResponseWriter, r *http.Request) {
51+
return func(w http.ResponseWriter, r *http.Request) {
52+
ctx := r.Context()
53+
alertID, ok := ctx.Value("alertID").(string)
54+
l := xlog.FromRequest(r)
55+
if !ok {
56+
http.Error(w, http.StatusText(422), 422)
3857
return
58+
}
59+
l.SetField("message", "Successfully returned alert: "+alertID)
60+
w.Header().Set("Content-Type", "application/json; charset=utf-8")
61+
w.Write(db.getAlert(string(alertID)))
62+
}
63+
}
3964

40-
case "POST":
41-
decoder := json.NewDecoder(r.Body)
42-
var alert alertData
43-
err := decoder.Decode(&alert)
44-
if err != nil {
45-
http.Error(w, err.Error(), http.StatusInternalServerError)
46-
return
47-
}
48-
err = db.putAlert(alert)
49-
if err != nil {
50-
http.Error(w, err.Error(), http.StatusInternalServerError)
51-
return
52-
}
65+
func deleteAlert(db store) func(w http.ResponseWriter, r *http.Request) {
66+
return func(w http.ResponseWriter, r *http.Request) {
67+
ctx := r.Context()
68+
alertID, ok := ctx.Value("alertID").(string)
69+
if !ok {
70+
http.Error(w, http.StatusText(422), 422)
71+
return
72+
}
5373

74+
data := db.getAlert(string(alertID))
75+
err := db.deleteAlert(string(alertID))
76+
l := xlog.FromRequest(r)
77+
if err != nil {
78+
l.Errorf("Wasn't able to delete %s: %s", alertID, err.Error())
79+
http.Error(w, err.Error(), http.StatusInternalServerError)
80+
} else {
81+
l.SetField("message", "Successfully deleted alert: "+alertID)
5482
w.Header().Set("Content-Type", "application/json; charset=utf-8")
55-
w.Header().Set("Location", "/api/alert/"+base64.URLEncoding.EncodeToString([]byte(alert.ID)))
56-
w.WriteHeader(201) // Status 201 -- created
57-
w.Write(db.getAlert(alert.ID))
58-
return
83+
w.Write(data)
84+
}
85+
return
86+
}
87+
}
5988

60-
case "DELETE":
61-
data := db.getAlert(string(alertID))
62-
err := db.deleteAlert(string(alertID))
63-
if err != nil {
64-
http.Error(w, err.Error(), http.StatusInternalServerError)
65-
} else {
66-
w.Header().Set("Content-Type", "application/json; charset=utf-8")
67-
w.Write(data)
68-
}
89+
func postAlert(db store) func(w http.ResponseWriter, r *http.Request) {
90+
return func(w http.ResponseWriter, r *http.Request) {
91+
decoder := json.NewDecoder(r.Body)
92+
var alert alertData
93+
err := decoder.Decode(&alert)
94+
l := xlog.FromRequest(r)
95+
if err != nil {
96+
l.Error("Wasn't able to decode the message body: " + err.Error())
97+
http.Error(w, err.Error(), http.StatusInternalServerError)
6998
return
70-
71-
default:
72-
http.Error(w, "Method not allowed", 405)
99+
}
100+
err = db.putAlert(alert)
101+
if err != nil {
102+
l.Error("Wasn't able to save the alert: " + err.Error())
103+
http.Error(w, err.Error(), http.StatusInternalServerError)
73104
return
74105
}
106+
l.SetField("message", "Successfully created alert: "+alert.ID)
107+
w.Header().Set("Content-Type", "application/json; charset=utf-8")
108+
w.Header().Set("Location", "/api/alert/"+base64.URLEncoding.EncodeToString([]byte(alert.ID)))
109+
w.WriteHeader(201) // Status 201 -- created
110+
w.Write(db.getAlert(alert.ID))
111+
return
75112
}
76113
}
77114

78-
func alertListHandler(db store) func(w http.ResponseWriter, r *http.Request) {
115+
func listAlerts(db store) func(w http.ResponseWriter, r *http.Request) {
79116
return func(w http.ResponseWriter, r *http.Request) {
80-
vars := mux.Vars(r)
81-
prefix := vars["prefix"]
82-
data, err := db.getAlertsByPrefix(prefix)
117+
prefix := chi.URLParam(r, "prefix")
118+
data, num, err := db.getAlertsByPrefix(prefix)
119+
l := xlog.FromRequest(r)
83120
if err != nil {
121+
l.Error("Wasn't able to get alerts: " + err.Error())
84122
http.Error(w, err.Error(), http.StatusInternalServerError)
85123
} else {
124+
l.SetField("message", fmt.Sprintf("Successfully returned %d alerts", num))
86125
w.Header().Set("Content-Type", "application/json; charset=utf-8")
87126
w.Write(data)
88127
}
@@ -91,6 +130,8 @@ func alertListHandler(db store) func(w http.ResponseWriter, r *http.Request) {
91130

92131
func boltBackupHandler(db store) func(w http.ResponseWriter, r *http.Request) {
93132
return func(w http.ResponseWriter, r *http.Request) {
133+
l := xlog.FromRequest(r)
94134
db.backup(w)
135+
l.SetField("message", "Database backup started")
95136
}
96137
}

main.go

+39-11
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package main
33
import (
44
"log"
55
"net/http"
6+
"os"
7+
"time"
68

7-
"github.com/gorilla/mux"
8-
"github.com/justinas/alice"
9+
"github.com/pressly/chi"
10+
"github.com/pressly/chi/middleware"
11+
"github.com/rs/xlog"
912
)
1013

1114
func main() {
@@ -16,15 +19,40 @@ func main() {
1619
}
1720
defer db.Close()
1821

19-
router := mux.NewRouter().StrictSlash(true)
20-
chain := alice.New(loggingHandler, timeoutHandler)
22+
host, _ := os.Hostname()
23+
r := chi.NewRouter()
2124

22-
router.HandleFunc("/api/backup", boltBackupHandler(db))
23-
router.HandleFunc("/api/alerts", alertListHandler(db))
24-
router.HandleFunc("/api/alerts/{prefix}", alertListHandler(db))
25-
router.HandleFunc("/api/alert", alertHandler(db))
26-
router.HandleFunc("/api/alert/{alertID}", alertHandler(db))
27-
router.PathPrefix("/").Handler(http.FileServer(http.Dir("./dashboard/")))
25+
r.Use(xlog.NewHandler(xlog.Config{
26+
Output: xlog.NewOutputChannel(xlog.NewConsoleOutput()),
27+
Fields: xlog.F{"hostname": host},
28+
}))
29+
r.Use(xlog.MethodHandler("method"))
30+
r.Use(xlog.URLHandler("url"))
31+
r.Use(xlog.RemoteAddrHandler("ip"))
32+
r.Use(xlog.RequestIDHandler("req_id", "Request-Id"))
33+
r.Use(logHandler)
34+
r.Use(middleware.Recoverer)
35+
r.Use(middleware.Timeout(5 * time.Second))
2836

29-
log.Fatal(http.ListenAndServe(":8080", chain.Then(router)))
37+
r.Route("/api", func(r chi.Router) {
38+
r.Route("/alert", func(r chi.Router) {
39+
r.Post("/", postAlert(db))
40+
r.Route("/:alertID", func(r chi.Router) {
41+
r.Use(alertCtx)
42+
r.Get("/", getAlert(db))
43+
r.Delete("/", deleteAlert(db))
44+
})
45+
})
46+
r.Route("/alerts", func(r chi.Router) {
47+
r.Get("/", listAlerts(db))
48+
r.Get("/:prefix", listAlerts(db))
49+
})
50+
r.Route("/backup", func(r chi.Router) {
51+
r.Get("/", boltBackupHandler(db))
52+
})
53+
})
54+
55+
r.FileServer("/", http.Dir("./dashboard/"))
56+
57+
log.Fatal(http.ListenAndServe(":8080", r))
3058
}

0 commit comments

Comments
 (0)