Skip to content

Commit c659de2

Browse files
committed
feat: added JSON logging
1 parent ed525c8 commit c659de2

File tree

8 files changed

+143
-57
lines changed

8 files changed

+143
-57
lines changed

filesystem.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"path/filepath"
1212
"sort"
1313
"strings"
14+
15+
"github.com/a-h/gemini/log"
1416
)
1517

1618
type Dir string
@@ -48,7 +50,7 @@ func DirectoryListingHandler(path string, f File) Handler {
4850
return HandlerFunc(func(w ResponseWriter, r *Request) {
4951
files, err := f.Readdir(-1)
5052
if err != nil {
51-
//TODO: Log.
53+
log.Error("DirectoryListingHandler: readdir failed", err, log.String("path", r.URL.Path), log.String("url", r.URL.String()))
5254
w.SetHeader(CodeTemporaryFailure, "readdir failed")
5355
return
5456
}
@@ -90,13 +92,13 @@ func FileSystemHandler(fs FileSystem) Handler {
9092
}
9193
f, err := fs.Open(r.URL.Path)
9294
if err != nil {
93-
//TODO: Log.
95+
log.Error("FileSystemHandler: file open failed", err, log.String("path", r.URL.Path), log.String("url", r.URL.String()))
9496
w.SetHeader(CodeTemporaryFailure, "file open failed")
9597
return
9698
}
9799
stat, err := f.Stat()
98100
if err != nil {
99-
//TODO: Log.
101+
log.Error("FileSystemHandler: file stat failed", err, log.String("path", r.URL.Path), log.String("url", r.URL.String()))
100102
w.SetHeader(CodeTemporaryFailure, "file stat failed")
101103
return
102104
}

go.mod

-10
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,3 @@
11
module github.com/a-h/gemini
22

33
go 1.14
4-
5-
require (
6-
github.com/gdamore/tcell v1.3.0
7-
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
8-
github.com/mattn/go-runewidth v0.0.9
9-
github.com/natefinch/atomic v0.0.0-20200526193002-18c0533a5b09
10-
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
11-
golang.org/x/sys v0.0.0-20200817155316-9781c653f443 // indirect
12-
golang.org/x/text v0.3.2 // indirect
13-
)

go.sum

-22
Original file line numberDiff line numberDiff line change
@@ -1,22 +0,0 @@
1-
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
2-
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
3-
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
4-
github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM=
5-
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
6-
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
7-
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
8-
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
9-
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
10-
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
11-
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
12-
github.com/natefinch/atomic v0.0.0-20200526193002-18c0533a5b09 h1:DXR0VtCesBD2ss3toN9OEeXszpQmW9dc3SvUbUfiBC0=
13-
github.com/natefinch/atomic v0.0.0-20200526193002-18c0533a5b09/go.mod h1:1rLVY/DWf3U6vSZgH16S7pymfrhK2lcUlXjgGglw/lY=
14-
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
15-
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
16-
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
17-
golang.org/x/sys v0.0.0-20200817155316-9781c653f443 h1:X18bCaipMcoJGm27Nv7zr4XYPKGUy92GtqboKC2Hxaw=
18-
golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
19-
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
20-
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
21-
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
22-
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

log/log.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package log
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"sync"
8+
"time"
9+
)
10+
11+
type Field func() (k string, v interface{})
12+
13+
func String(name string, value string) Field {
14+
return func() (k string, v interface{}) {
15+
return name, value
16+
}
17+
}
18+
func Int(name string, value int) Field {
19+
return func() (k string, v interface{}) {
20+
return name, value
21+
}
22+
}
23+
func Int64(name string, value int64) Field {
24+
return func() (k string, v interface{}) {
25+
return name, value
26+
}
27+
}
28+
func Interface(name string, value interface{}) Field {
29+
return func() (k string, v interface{}) {
30+
return name, value
31+
}
32+
}
33+
34+
func Info(msg string, fields ...Field) {
35+
std.Write("INFO", msg, fields...)
36+
}
37+
38+
func Warn(msg string, fields ...Field) {
39+
std.Write("WARN", msg, fields...)
40+
}
41+
42+
func Error(msg string, err error, fields ...Field) {
43+
if err != nil {
44+
fields = append(fields, String("error", err.Error()))
45+
}
46+
std.Write("ERROR", msg, fields...)
47+
}
48+
49+
type Logger struct {
50+
stdout sync.Mutex
51+
stderr sync.Mutex
52+
enc json.Encoder
53+
newLine []byte
54+
}
55+
56+
func (s *Logger) Write(level string, msg string, fields ...Field) {
57+
entry := make(map[string]interface{})
58+
entry["time"] = time.Now().UTC().Format(time.RFC3339)
59+
entry["level"] = level
60+
entry["msg"] = msg
61+
for i := 0; i < len(fields); i++ {
62+
k, v := fields[i]()
63+
entry[k] = v
64+
}
65+
j, err := json.Marshal(entry)
66+
if err != nil {
67+
j = []byte(fmt.Sprintf("log.Write: failed to marshal entry '%+v': %v", entry, err))
68+
}
69+
if level == "ERROR" {
70+
s.stderr.Lock()
71+
defer s.stderr.Unlock()
72+
os.Stderr.Write(j)
73+
os.Stderr.Write(s.newLine)
74+
return
75+
}
76+
s.stdout.Lock()
77+
defer s.stdout.Unlock()
78+
os.Stdout.Write(j)
79+
os.Stderr.Write(s.newLine)
80+
}
81+
82+
var std = &Logger{newLine: []byte("\n")}

server.go

+28-22
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import (
88
"errors"
99
"fmt"
1010
"io"
11-
"log"
1211
"net"
1312
"net/url"
13+
"reflect"
1414
"strings"
1515
"time"
16+
17+
"github.com/a-h/gemini/log"
1618
)
1719

1820
// Handler of Gemini content.
@@ -114,10 +116,6 @@ type Server struct {
114116
HandlerTimeout time.Duration
115117
}
116118

117-
func (srv *Server) logf(format string, v ...interface{}) {
118-
log.Printf(format, v...)
119-
}
120-
121119
// Set the server listening on the specified port.
122120
func (srv *Server) ListenAndServe() error {
123121
// Don't start if the server is already closing down.
@@ -128,7 +126,7 @@ func (srv *Server) ListenAndServe() error {
128126
if addr == "" {
129127
addr = ":1965"
130128
}
131-
srv.logf("gemini: starting on %v", addr)
129+
log.Info("gemini: starting", log.String("addr", addr))
132130
ln, err := net.Listen("tcp", addr)
133131
if err != nil {
134132
return err
@@ -137,15 +135,15 @@ func (srv *Server) ListenAndServe() error {
137135
if srv.Insecure {
138136
err = srv.serveInsecure(ln)
139137
if err != nil {
140-
srv.logf("gemini: failure in serveInsecure")
138+
log.Error("gemini: serveInsecure failure", err, log.String("addr", addr))
141139
}
142140
} else {
143141
err = srv.serveTLS(ln)
144142
if err != nil {
145-
srv.logf("gemini: failure in serveTLS: %v", err)
143+
log.Error("gemini: serveTLS failure", err, log.String("addr", addr))
146144
}
147145
}
148-
srv.logf("gemini: stopped")
146+
log.Info("gemini: stopped")
149147
return err
150148
}
151149

@@ -162,12 +160,12 @@ func (srv *Server) serveInsecure(l net.Listener) (err error) {
162160
}
163161
for {
164162
if err = srv.Context.Err(); err != nil {
165-
srv.logf("gemini: context caused shutdown: %v", err)
163+
log.Error("gemini: context caused shutdown", err)
166164
return err
167165
}
168166
rw, err := l.Accept()
169167
if err != nil {
170-
srv.logf("gemini: insecure listener error: %v", err)
168+
log.Error("gemini: insecure listener error", err)
171169
continue
172170
}
173171
go func() {
@@ -196,12 +194,12 @@ func (srv *Server) serveTLS(l net.Listener) (err error) {
196194
tlsListener := tls.NewListener(l, config)
197195
for {
198196
if err = srv.Context.Err(); err != nil {
199-
srv.logf("gemini: context caused shutdown: %v", err)
197+
log.Error("gemini: context caused shutdown", err)
200198
return err
201199
}
202200
conn, err := tlsListener.Accept()
203201
if err != nil {
204-
srv.logf("gemini: tls listener error: %v", err)
202+
log.Error("gemini: tls listener error", err)
205203
continue
206204
}
207205
tlsConn, ok := conn.(*tls.Conn)
@@ -215,7 +213,7 @@ func (srv *Server) serveTLS(l net.Listener) (err error) {
215213
func (srv *Server) handleTLS(conn *tls.Conn) {
216214
defer conn.Close()
217215
if err := conn.Handshake(); err != nil {
218-
srv.logf("gemini: failed TLS handshake from %s: %v", conn.RemoteAddr(), err)
216+
log.Info("gemini: failed TLS handshake", log.String("remote", conn.RemoteAddr().String()), log.String("reason", err.Error()))
219217
return
220218
}
221219
var certificate Certificate
@@ -235,7 +233,7 @@ func (srv *Server) handleTLS(conn *tls.Conn) {
235233
serverName := conn.ConnectionState().ServerName
236234
dh, ok := srv.DomainToHandler[serverName]
237235
if !ok {
238-
srv.logf("gemini: failed to find domain handler for serverName: %q", serverName)
236+
log.Warn("gemini: failed to find domain handler", log.String("serverName", serverName))
239237
}
240238
srv.handle(dh, certificate, conn)
241239
}
@@ -246,11 +244,10 @@ func (srv *Server) handle(dh *DomainHandler, certificate Certificate, conn net.C
246244
conn.SetReadDeadline(time.Now().Add(srv.ReadTimeout))
247245
r, ok, err := srv.parseRequest(conn)
248246
if err != nil {
249-
srv.logf("gemini: failed to parse request: %v", err)
247+
log.Info("gemini: failed to parse request", log.String("reason", err.Error()))
250248
return
251249
}
252250
if !ok {
253-
srv.logf("gemini: could not parse request")
254251
return
255252
}
256253
r.Certificate = certificate
@@ -261,7 +258,7 @@ func (srv *Server) handle(dh *DomainHandler, certificate Certificate, conn net.C
261258
w := NewWriter(conn)
262259
defer func() {
263260
if p := recover(); p != nil {
264-
srv.logf("gemini: server error: %v: %v", r.URL.Path, p)
261+
log.Error("gemini: server error", nil, log.String("url", r.URL.String()), log.Interface("recover", p))
265262
w.SetHeader(CodeCGIError, "internal error")
266263
}
267264
}()
@@ -271,10 +268,18 @@ func (srv *Server) handle(dh *DomainHandler, certificate Certificate, conn net.C
271268
}
272269
dh.Handler.ServeGemini(w, r)
273270
if w.Code == "" {
274-
srv.logf("gemini: handler for %q resulted in empty response, sending empty document", r.URL.Path)
271+
log.Error("gemini: handler resulted in empty response", nil, log.String("url", r.URL.String()), log.String("handlerType", reflect.TypeOf(dh.Handler).PkgPath()))
275272
w.SetHeader(CodeCGIError, "empty response")
276273
}
277-
srv.logf("gemini: %v response: %v time: %v", r.URL.Path, w.Code, time.Now().Sub(start))
274+
duration := time.Now().Sub(start)
275+
log.Info("gemini: response",
276+
log.String("url", r.URL.String()),
277+
log.String("path", r.URL.Path),
278+
log.String("code", w.Code),
279+
log.String("handlerType", reflect.TypeOf(dh.Handler).PkgPath()),
280+
log.Int64("ms", duration.Milliseconds()),
281+
log.Int64("us", int64(duration.Microseconds())),
282+
)
278283
}
279284

280285
func (srv *Server) parseRequest(rw io.ReadWriter) (r *Request, ok bool, err error) {
@@ -284,17 +289,18 @@ func (srv *Server) parseRequest(rw io.ReadWriter) (r *Request, ok bool, err erro
284289
return
285290
}
286291
if !ok {
292+
log.Info("gemini: request too long or malformed", log.String("request", string(request)))
287293
writeHeaderToWriter(CodeBadRequest, "request too long or malformed", rw)
288294
return
289295
}
290296
ok = false
291297
url, err := url.Parse(strings.TrimSpace(string(request)))
292298
if err != nil {
293-
srv.logf("gemini: malformed request: %q", string(request))
299+
log.Info("gemini: malformed request", log.String("request", string(request)))
294300
writeHeaderToWriter(CodeBadRequest, "request malformed", rw)
295301
return
296302
}
297-
srv.logf("gemini: received request: %s", url)
303+
log.Info("gemini: received request", log.String("request", url.String()))
298304
r = &Request{
299305
URL: url,
300306
}

server_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package gemini
22

33
import (
44
"bytes"
5+
"context"
56
"io/ioutil"
7+
"net"
68
"reflect"
79
"strings"
810
"testing"
11+
"time"
912
)
1013

1114
func TestServer(t *testing.T) {
@@ -186,6 +189,7 @@ func TestServer(t *testing.T) {
186189
DomainToHandler: map[string]*DomainHandler{
187190
"": dh,
188191
},
192+
Context: context.Background(),
189193
}
190194
s.handle(dh, tt.cert, rec)
191195

@@ -241,3 +245,27 @@ func (rec *Recorder) Read(p []byte) (n int, err error) {
241245
rec.read += n
242246
return n, err
243247
}
248+
249+
func (rec *Recorder) Close() error {
250+
return nil
251+
}
252+
253+
func (rec *Recorder) LocalAddr() net.Addr {
254+
return &net.IPAddr{}
255+
}
256+
257+
func (rec *Recorder) RemoteAddr() net.Addr {
258+
return &net.IPAddr{}
259+
}
260+
261+
func (rec *Recorder) SetDeadline(t time.Time) error {
262+
return nil
263+
}
264+
265+
func (rec *Recorder) SetReadDeadline(t time.Time) error {
266+
return nil
267+
}
268+
269+
func (rec *Recorder) SetWriteDeadline(t time.Time) error {
270+
return nil
271+
}
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)