blob: 2aa76e40f73b9440adac4735a0ef1443d854ebb2 [file] [log] [blame]
Serge Bazanskicc25bdf2018-10-25 14:02:58 +02001// Copyright 2018 Serge Bazanski
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Based off github.com/youtube/doorman/blob/master/go/status/status.go
16
17// Copyright 2016 Google, Inc.
18//
19// Licensed under the Apache License, Version 2.0 (the "License");
20// you may not use this file except in compliance with the License.
21// You may obtain a copy of the License at
22//
23// http://www.apache.org/licenses/LICENSE-2.0
24//
25// Unless required by applicable law or agreed to in writing, software
26// distributed under the License is distributed on an "AS IS" BASIS,
27// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
28// See the License for the specific language governing permissions and
29// limitations under the License.
30
31package statusz
32
33import (
34 "bytes"
35 "context"
36 "crypto/sha256"
37 "fmt"
38 "html"
39 "html/template"
40 "io"
41 "net/http"
42 "os"
43 "os/user"
44 "path/filepath"
45 "sync"
46 "time"
47
48 "github.com/golang/glog"
Serge Bazanskicc25bdf2018-10-25 14:02:58 +020049)
50
51var (
52 binaryName = filepath.Base(os.Args[0])
53 binaryHash string
54 hostname string
55 username string
56 serverStart = time.Now()
57
58 lock sync.Mutex
59 sections []section
60 tmpl = template.Must(reparse(nil))
61 funcs = make(template.FuncMap)
62
63 DefaultMux = true
64)
65
66type section struct {
67 Banner string
68 Fragment string
69 F func() interface{}
70}
71
72var statusHTML = `<!DOCTYPE html>
73<html>
74<head>
75<title>Status for {{.BinaryName}}</title>
76<style>
77body {
78font-family: sans-serif;
79background: #fff;
80}
81h1 {
82clear: both;
83width: 100%;
84text-align: center;
85font-size: 120%;
86background: #eeeeff;
87}
88.lefthand {
89float: left;
90width: 80%;
91}
92.righthand {
93text-align: right;
94}
95</style>
96</head>
97<h1>Status for {{.BinaryName}}</h1>
98<div>
99<div class=lefthand>
100Started at {{.StartTime}}<br>
101Current time {{.CurrentTime}}<br>
102SHA256 {{.BinaryHash}}<br>
103</div>
104<div class=righthand>
105Running as {{.Username}} on {{.Hostname}}<br>
106Load {{.LoadAvg}}<br>
107View <a href=/debug/status>status</a>,
108 <a href=/debug/requests>requests</a>
109</div>
110</div>`
111
112func reparse(sections []section) (*template.Template, error) {
113 var buf bytes.Buffer
114
115 io.WriteString(&buf, `{{define "status"}}`)
116 io.WriteString(&buf, statusHTML)
117
118 for i, sec := range sections {
119 fmt.Fprintf(&buf, "<h1>%s</h1>\n", html.EscapeString(sec.Banner))
120 fmt.Fprintf(&buf, "{{$sec := index .Sections %d}}\n", i)
121 fmt.Fprintf(&buf, `{{template "sec-%d" call $sec.F}}`+"\n", i)
122 }
123 fmt.Fprintf(&buf, `</html>`)
124 io.WriteString(&buf, "{{end}}\n")
125
126 for i, sec := range sections {
127 fmt.Fprintf(&buf, `{{define "sec-%d"}}%s{{end}}\n`, i, sec.Fragment)
128 }
129 return template.New("").Funcs(funcs).Parse(buf.String())
130}
131
132func StatusHandler(w http.ResponseWriter, r *http.Request) {
133 lock.Lock()
134 defer lock.Unlock()
135
Serge Bazanskicc25bdf2018-10-25 14:02:58 +0200136 data := struct {
137 Sections []section
138 BinaryName string
139 BinaryHash string
140 Hostname string
141 Username string
142 StartTime string
143 CurrentTime string
144 LoadAvg string
145 }{
146 Sections: sections,
147 BinaryName: binaryName,
148 BinaryHash: binaryHash,
149 Hostname: hostname,
150 Username: username,
151 StartTime: serverStart.Format(time.RFC1123),
152 CurrentTime: time.Now().Format(time.RFC1123),
Sergiusz Bazanski1affad42019-04-06 01:21:04 +0200153 LoadAvg: loadAverage(),
Serge Bazanskicc25bdf2018-10-25 14:02:58 +0200154 }
155
156 if err := tmpl.ExecuteTemplate(w, "status", data); err != nil {
157 glog.Errorf("servenv: couldn't execute template: %v", err)
158 }
159}
160
161func init() {
162 var err error
163 hostname, err = os.Hostname()
164 if err != nil {
165 glog.Fatalf("os.Hostname: %v", err)
166 }
167
168 user, err := user.Current()
169 if err != nil {
170 glog.Fatalf("user.Current: %v", err)
171 }
172 username = fmt.Sprintf("%s (%s)", user.Username, user.Uid)
173
Serge Bazanski8fab2be2018-10-25 23:37:37 +0200174 exec, err := os.Executable()
175 if err == nil {
176 f, err := os.Open(exec)
177 if err == nil {
178 h := sha256.New()
179 if _, err := io.Copy(h, f); err != nil {
180 glog.Fatalf("io.Copy: %v", err)
181 }
182 binaryHash = fmt.Sprintf("%x", h.Sum(nil))
183 } else {
184 glog.Errorf("Could not get SHA256 of binary: os.Open(%q): %v", exec, err)
185 binaryHash = "could not read executable"
186 }
187 } else {
188 glog.Errorf("Could not get SHA256 of binary: os.Executable(): %v", err)
189 binaryHash = "could not get executable"
Serge Bazanskicc25bdf2018-10-25 14:02:58 +0200190 }
Serge Bazanskicc25bdf2018-10-25 14:02:58 +0200191
192 if DefaultMux {
193 http.HandleFunc("/debug/status", StatusHandler)
194 }
195}
196
197// AddStatusPart adds a new section to status. frag is used as a
198// subtemplate of the template used to render /debug/status, and will
199// be executed using the value of invoking f at the time of the
200// /debug/status request. frag is parsed and executed with the
201// html/template package. Functions registered with AddStatusFuncs
202// may be used in the template.
203func AddStatusPart(banner, frag string, f func(context.Context) interface{}) {
204 lock.Lock()
205 defer lock.Unlock()
206
207 secs := append(sections, section{
208 Banner: banner,
209 Fragment: frag,
210 F: func() interface{} { return f(context.Background()) },
211 })
212
213 var err error
214 tmpl, err = reparse(secs)
215 if err != nil {
216 secs[len(secs)-1] = section{
217 Banner: banner,
218 Fragment: "<code>bad status template: {{.}}</code>",
219 F: func() interface{} { return err },
220 }
221 }
222 tmpl, _ = reparse(secs)
223 sections = secs
224}
225
226// AddStatusSection registers a function that generates extra
227// information for /debug/status. If banner is not empty, it will be
228// used as a header before the information. If more complex output
229// than a simple string is required use AddStatusPart instead.
230func AddStatusSection(banner string, f func(context.Context) string) {
231 AddStatusPart(banner, `{{.}}`, func(ctx context.Context) interface{} { return f(ctx) })
232}