/
healthd.go
120 lines (109 loc) · 3.78 KB
/
healthd.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// This file is part of graze/golang-service
//
// Copyright (c) 2016 Nature Delivered Ltd. <https://www.graze.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//
// license: https://github.com/graze/golang-service/blob/master/LICENSE
// link: https://github.com/graze/golang-service
package handlers
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"time"
)
type healthdHandler struct {
writer io.Writer
handler http.Handler
}
// ServeHTTP does the actual handling of HTTP requests by wrapping the request in a logger
func (h healthdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
LogServeHTTP(w, req, h.handler, h.writeLog)
}
// writeLog writes a log entry to healthdHandler's writer
func (h healthdHandler) writeLog(w LoggingResponseWriter, req *http.Request, url url.URL, ts time.Time, dur time.Duration, status, size int) {
writeHealthdLog(h.writer, req, url, ts, dur, status, size)
}
// writeHealthdLog writes a log entry for req to w in healthd format.
// ts is the timestamp with wich the entry should be logged
// dur is the time taken by the server to generate the response
// status and size are used to provide response HTTP status and size
//
// The format of the file is:
// <unix_timestamp.ms>"<path>"<status>"<request_time>"<upstream_time>"<X-Forwarded-For header>
func writeHealthdLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, dur time.Duration, status, size int) {
uri := parseURI(req, url)
msDur := float64(dur.Nanoseconds()) / (float64(time.Second) / float64(time.Nanosecond))
str := fmt.Sprintf(`%.3f%s%d"%.3f"%.3f"%s`+"\n",
float64(ts.UnixNano())/(float64(time.Second)/float64(time.Nanosecond)),
strconv.Quote(uri),
status,
msDur,
msDur,
req.Header.Get("X-Forwarded-For"))
io.WriteString(w, str)
}
// HealthdIoHandler return a http.Handler that wraps h and logs request to out in
// nginx Healthd format
//
// see http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/health-enhanced-serverlogs.html
//
// Example:
//
// r := mux.NewRouter()
// r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// w.Write([]byte("This is a catch-all route"))
// })
// loggedRouter := handlers.HealthdIoHandler(os.Stdout, r)
// http.ListenAndServe(":1123", loggedRouter)
func HealthdIoHandler(out io.Writer, h http.Handler) http.Handler {
return healthdHandler{out, h}
}
type healthdFileHandler struct {
path string
base http.Handler
handler http.Handler
timestamp string
}
// ServerHTTP for the healthdFileHandler automatically rotates the log files based on the hour
func (h healthdFileHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
now := time.Now().UTC().Format("2006-01-02-15")
if h.timestamp != now || h.base == nil {
h.timestamp = now
file := h.path + "application.log." + now
logFile, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
panic(err)
}
defer logFile.Close()
h.base = HealthdIoHandler(logFile, h.handler)
}
h.base.ServeHTTP(w, req)
}
// HealthdHandler return a http.Handler that wraps h and logs request to out in
// nginx Healthd format to /var/log/nginx/healthd/application.log.<year>-<month>-<day>-<hour>
//
// see http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/health-enhanced-serverlogs.html
//
// Example:
//
// r := mux.NewRouter()
// r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// w.Write([]byte("This is a catch-all route"))
// })
// loggedRouter := handlers.HealthdHandler(r)
// http.ListenAndServe(":1123", loggedRouter)
//
func HealthdHandler(h http.Handler) http.Handler {
path := "/var/log/nginx/healthd/"
err := os.MkdirAll(path, 0666)
if err != nil {
panic(err)
}
return healthdFileHandler{path, nil, h, ""}
}