-
Notifications
You must be signed in to change notification settings - Fork 8
/
logs.go
139 lines (120 loc) · 3.76 KB
/
logs.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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package main
import (
"bytes"
"compress/bzip2"
"fmt"
"io"
"io/ioutil"
"net/http"
"path"
"regexp"
"strconv"
"strings"
"time"
"cloud.google.com/go/storage"
"golang.org/x/net/context"
"google.golang.org/appengine"
"google.golang.org/appengine/datastore"
"google.golang.org/appengine/log"
)
const (
fileName = `[a-zA-Z0-9-_/.]+\.[ch]`
identifier = `[_a-zA-Z][_a-zA-Z0-9]{0,30}`
lineNumber = `[0-9]+`
defaultBucket = `i3-github-bot.appspot.com`
)
// Matches an i3 log line, such as:
// 2015-02-01 17:21:48 - ../i3-4.8/src/handlers.c:handle_event:1231 - blah
// (cannot match the date/time since that is locale-specific)
var i3LogLine = regexp.MustCompile(` - ` + fileName + `:` + identifier + `:` + lineNumber + ` - `)
type Blobref struct {
// TODO: remove this now-unused attribute (we are storing objects in Google
// Cloud Storage now, not blobstore).
Blobkey appengine.BlobKey
Filename string
}
func logsHandler(w http.ResponseWriter, r *http.Request) {
var blobref Blobref
ctx := appengine.NewContext(r)
strid := path.Base(r.URL.Path)
if strings.HasSuffix(strid, ".bz2") {
strid = strid[:len(strid)-len(".bz2")]
}
intid, err := strconv.ParseInt(strid, 0, 64)
if err != nil {
log.Errorf(ctx, "strconv.ParseInt: %v", err)
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if err := datastore.Get(ctx, datastore.NewKey(ctx, "blobref", "", intid, nil), &blobref); err != nil {
log.Errorf(ctx, "datastore.Get: %v", err)
http.Error(w, err.Error(), http.StatusNotFound)
return
}
client, err := storage.NewClient(ctx)
if err != nil {
log.Errorf(ctx, "NewReader: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
rc, err := client.Bucket(defaultBucket).Object(blobref.Filename).NewReader(ctx)
if err != nil {
log.Errorf(ctx, "NewReader: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rc.Close()
w.Header().Set("Content-Type", "application/octet-stream")
if _, err := io.Copy(w, rc); err != nil {
log.Errorf(ctx, "Copy: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func writeBlob(ctx context.Context, r io.Reader) (string, error) {
filename := strconv.FormatInt(time.Now().UnixNano(), 10)
client, err := storage.NewClient(ctx)
if err != nil {
return "", err
}
bw := client.Bucket(defaultBucket).Object(filename).NewWriter(ctx)
bw.ContentType = "application/octet-stream"
bw.ACL = []storage.ACLRule{{Entity: storage.AllUsers, Role: storage.RoleReader}}
if _, err := io.Copy(bw, r); err != nil {
return "", err
}
if err := bw.Close(); err != nil {
return "", err
}
return filename, nil
}
// TODO: wrap this so that errors contain an instruction on how to use the service.
// logHandler takes a compressed i3 debug log and stores it on
// Google Cloud Storage.
func logHandler(w http.ResponseWriter, r *http.Request) {
var body bytes.Buffer
rd := bzip2.NewReader(io.TeeReader(r.Body, &body))
uncompressed, err := ioutil.ReadAll(rd)
if err != nil {
http.Error(w, "Data not bzip2-compressed.", http.StatusBadRequest)
return
}
// TODO: match line by line, and have a certain percentage that needs to be an i3 log
// TODO: also allow strace log files
if !i3LogLine.Match(uncompressed) {
http.Error(w, "Data is not an i3 log file.", http.StatusBadRequest)
return
}
ctx := appengine.NewContext(r)
filename, err := writeBlob(ctx, &body)
if err != nil {
http.Error(w, fmt.Sprintf("cloud storage: %v", err), http.StatusInternalServerError)
return
}
key, err := datastore.Put(ctx, datastore.NewIncompleteKey(ctx, "blobref", nil), &Blobref{Filename: filename})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "https://logs.i3wm.org/logs/%d.bz2\n", key.IntID())
}