Skip to content

Commit b2f8e55

Browse files
authored
Merge pull request #35 from grafana/add-query-frontend-parse
Adding logtool
2 parents 707a9a5 + cfa1e34 commit b2f8e55

File tree

5 files changed

+239
-19
lines changed

5 files changed

+239
-19
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
cmd/cortextool/cortextool
22
cmd/chunktool/chunktool
3+
cmd/logtool/logtool
34
.uptodate
45
.pkg
56
.cache

Makefile

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ GIT_REVISION := $(shell git rev-parse --short HEAD)
77
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
88
GO_FLAGS := -mod=vendor -ldflags "-extldflags \"-static\" -s -w -X $(VPREFIX).Branch=$(GIT_BRANCH) -X $(VPREFIX).Version=$(IMAGE_TAG) -X $(VPREFIX).Revision=$(GIT_REVISION)" -tags netgo
99

10-
all: cortextool chunktool
11-
images: cortextool-image chunktool-image
10+
all: cortextool chunktool logtool
11+
images: cortextool-image chunktool-image logtool-image
1212
cortextool: cmd/cortextool/cortextool
1313
chunktool: cmd/chunktool/chunktool
14+
logtool: cmd/logtool/logtool
1415

1516
cortextool-image:
1617
$(SUDO) docker build -t $(IMAGE_PREFIX)/cortextool -f cmd/cortextool/Dockerfile .
@@ -20,22 +21,31 @@ chunktool-image:
2021
$(SUDO) docker build -t $(IMAGE_PREFIX)/chunktool -f cmd/chunktool/Dockerfile .
2122
$(SUDO) docker tag $(IMAGE_PREFIX)/chunktool $(IMAGE_PREFIX)/chunktool:$(IMAGE_TAG)
2223

24+
logtool-image:
25+
$(SUDO) docker build -t $(IMAGE_PREFIX)/logtool -f cmd/logtool/Dockerfile .
26+
$(SUDO) docker tag $(IMAGE_PREFIX)/logtool $(IMAGE_PREFIX)/logtool:$(IMAGE_TAG)
27+
2328
cmd/cortextool/cortextool: $(APP_GO_FILES) cmd/cortextool/main.go
2429
CGO_ENABLED=0 go build $(GO_FLAGS) -o $@ ./$(@D)
2530

2631
cmd/chunktool/chunktool: $(APP_GO_FILES) cmd/chunktool/main.go
2732
CGO_ENABLED=0 go build $(GO_FLAGS) -o $@ ./$(@D)
2833

34+
cmd/logtool/logtool: $(APP_GO_FILES) cmd/logtool/main.go
35+
CGO_ENABLED=0 go build $(GO_FLAGS) -o $@ ./$(@D)
36+
2937
lint:
3038
golangci-lint run -v
3139

3240
cross:
3341
CGO_ENABLED=0 gox -output="dist/{{.Dir}}-{{.OS}}-{{.Arch}}" -ldflags=${LDFLAGS} -arch="amd64" -os="linux windows darwin" ./cmd/cortextool
3442
CGO_ENABLED=0 gox -output="dist/{{.Dir}}-{{.OS}}-{{.Arch}}" -ldflags=${LDFLAGS} -arch="amd64" -os="linux windows darwin" ./cmd/chunktool
43+
CGO_ENABLED=0 gox -output="dist/{{.Dir}}-{{.OS}}-{{.Arch}}" -ldflags=${LDFLAGS} -arch="amd64" -os="linux windows darwin" ./cmd/logtool
3544

3645
test:
3746
go test -mod=vendor -p=8 ./...
3847

3948
clean:
4049
rm -rf cmd/cortextool/cortextool
4150
rm -rf cmd/chunktool/chunktool
51+
rm -rf cmd/logtool/logtool

README.md

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,68 @@
1-
# cortextool
1+
# Cortex Tools
22

3-
This tool is designed to interact with the various user facing APIs provided by Cortex, as well as, interact with various backend storage components containing Cortex data.
3+
This repo contains tools used for interacting with [Cortex](https://github.com/cortexproject/cortex).
44

5-
## Config Commands
5+
* [cortextool](#cortextool): Interacts with user-facing Cortex APIs and backend storage components
6+
* [chunktool](#chunktool): Interacts with chunks stored and indexed in Cortex storage backends.
7+
* [logtool](#logtool): Tool which parses Cortex query-frontend logs and formats them for easy analysis.
8+
9+
## cortextool
10+
11+
This tool is designed to interact with the various user-facing APIs provided by Cortex, as well as, interact with various backend storage components containing Cortex data.
12+
13+
### Config Commands
614

715
Config commands interact with the Cortex api and read/create/update/delete user configs from Cortex. Specifically a users alertmanager and rule configs can be composed and updated using these commands.
816

9-
### Configuration
17+
#### Configuration
1018

1119
| Env Variables | Flag | Description |
1220
| ----------------- | --------- | ------------------------------------------------------------------------------------------------------------- |
1321
| CORTEX_ADDRESS | `address` | Addess of the API of the desired Cortex cluster. |
1422
| CORTEX_API_KEY | `key` | In cases where the Cortex API is set behind a basic auth gateway, an key can be set as a basic auth password. |
1523
| CORTEX_TENANT_ID | `id` | The tenant ID of the Cortex instance to interact with. |
1624

17-
### Alertmanager
25+
#### Alertmanager
1826

1927
The following commands are used by users to interact with their Cortex alertmanager configuration, as well as their alert template files.
2028

21-
#### Alertmanager Get
29+
##### Alertmanager Get
2230

2331
cortextool alertmanager get
2432

25-
#### Alertmanager Load
33+
##### Alertmanager Load
2634

2735
cortextool alertmanager load ./example_alertmanager_config.yaml
2836

29-
### Rules
37+
#### Rules
3038

3139
The following commands are used by users to interact with their Cortex ruler configuration. They can load prometheus rule files, as well as interact with individual rule groups.
3240

33-
#### Rules List
41+
##### Rules List
3442

3543
This command will retrieve all of the rule groups stored in the specified Cortex instance and print each one by rule group name and namespace to the terminal.
3644

3745
cortextool rules list
3846

39-
#### Rules Print
47+
##### Rules Print
4048

4149
This command will retrieve all of the rule groups stored in the specified Cortex instance and print them to the terminal.
4250

4351
cortextool rules print
4452

45-
#### Rules Get
53+
##### Rules Get
4654

4755
This command will retrieve the specified rule group from Cortex and print it to the terminal.
4856

4957
cortextool rules get example_namespace example_rule_group
5058

51-
#### Rules Delete
59+
##### Rules Delete
5260

5361
This command will retrieve the specified rule group from Cortex and print it to the terminal.
5462

5563
cortextool rules delete example_namespace example_rule_group
5664

57-
#### Rules Load
65+
##### Rules Load
5866

5967
This command will load each rule group in the specified files and load them into Cortex. If a rule already exists in Cortex it will be overwritten if a diff is found.
6068

@@ -82,21 +90,58 @@ At the end of the run, the command tells you whenever the operation was a succes
8290

8391
It is important to note that a modification can be a PromQL expression lint or a label add to your aggregation.
8492

85-
# chunktool
93+
## chunktool
8694

8795
This repo also contains the `chunktool`. A client meant to interact with chunks stored and indexed in cortex backends.
8896

89-
### Chunk Delete
97+
##### Chunk Delete
9098

9199
The delete command currently cleans all index entries pointing to chunks in the specified index. Only bigtable and the v10 schema are currently fully supported. This will not delete the entire index entry, only the corresponding chunk entries within the index row.
92100

93-
### Chunk Migrate
101+
##### Chunk Migrate
94102

95103
The migrate command helps with migrating chunks across cortex clusters. It also takes care of setting right index in the new cluster as per the specified schema config.
96104

97105
As of now it only supports `Bigtable` or `GCS` as a source to read chunks from for migration while for writing it supports all the storages that Cortex supports.
98106
More details about it [here](./pkg/chunk/migrate/README.md)
99107

100-
## License
108+
## logtool
109+
110+
A CLI tool to parse Cortex query-frontend logs and formats them for easy analysis.
111+
112+
```
113+
Options:
114+
-dur duration
115+
only show queries which took longer than this duration, e.g. -dur 10s
116+
-query
117+
show the query
118+
-utc
119+
show timestamp in UTC time
120+
```
121+
122+
Feed logs into it using [`logcli`](https://github.com/grafana/loki/blob/master/docs/getting-started/logcli.md) from Loki, [`kubectl`](https://kubernetes.io/docs/reference/kubectl/overview/) for Kubernetes, `cat` from a file, or any other way to get raw logs:
123+
124+
Loki `logcli` example:
125+
```
126+
$ logcli query '{cluster="us-central1", name="query-frontend", namespace="dev"}' --limit=5000 --since=3h --forward -o raw | ./logtool -dur 5s
127+
https://logs-dev-ops-tools1.grafana.net/loki/api/v1/query_range?direction=FORWARD&end=1591119479093405000&limit=5000&query=%7Bcluster%3D%22us-central1%22%2C+name%3D%22query-frontend%22%2C+namespace%3D%22dev%22%7D&start=1591108679093405000
128+
Common labels: {cluster="us-central1", container_name="query-frontend", job="dev/query-frontend", level="debug", name="query-frontend", namespace="dev", pod_template_hash="7cd4bf469d", stream="stderr"}
129+
130+
Timestamp TraceID Length Duration Status Path
131+
2020-06-02 10:38:40.34205349 -0400 EDT 1f2533b40f7711d3 12h0m0s 21.92465802s (200) /api/prom/api/v1/query_range
132+
2020-06-02 10:40:25.171649132 -0400 EDT 2ac59421db0000d8 168h0m0s 16.378698276s (200) /api/prom/api/v1/query_range
133+
2020-06-02 10:40:29.698167258 -0400 EDT 3fd088d900160ba8 168h0m0s 20.912864541s (200) /api/prom/api/v1/query_range
134+
```
135+
136+
```
137+
$ cat query-frontend-logs.log | ./logtool -dur 5s
138+
Timestamp TraceID Length Duration Status Path
139+
2020-05-26 13:51:15.0577354 -0400 EDT 76b9939fd5c78b8f 6h0m0s 10.249149614s (200) /api/prom/api/v1/query_range
140+
2020-05-26 13:52:15.771988849 -0400 EDT 2e7473ab10160630 10h33m0s 7.472855362s (200) /api/prom/api/v1/query_range
141+
2020-05-26 13:53:46.712563497 -0400 EDT 761f3221dcdd85de 10h33m0s 11.874296689s (200) /api/prom/api/v1/query_range
142+
```
143+
144+
145+
### License
101146

102147
Licensed Apache 2.0, see [LICENSE](LICENSE).

cmd/logtool/Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM golang:1.12.5-stretch as build
2+
ARG GOARCH="amd64"
3+
COPY . /build_dir
4+
WORKDIR /build_dir
5+
ENV GOPROXY=https://proxy.golang.org
6+
RUN make clean && make logtool
7+
8+
FROM alpine:3.9
9+
RUN apk add --update --no-cache ca-certificates
10+
COPY --from=build /build_dir/cmd/logtool/logtool /usr/bin/logtool
11+
EXPOSE 80
12+
ENTRYPOINT [ "/usr/bin/logtool" ]

cmd/logtool/main.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"flag"
6+
"fmt"
7+
"net/url"
8+
"os"
9+
"regexp"
10+
"strconv"
11+
"strings"
12+
"text/tabwriter"
13+
"time"
14+
)
15+
16+
var (
17+
rex = regexp.MustCompile("(\\S+)=(\".*?\"|\\S+)")
18+
layout = "2006-01-02T15:04:05.999999999Z"
19+
)
20+
21+
func main() {
22+
23+
fi, _ := os.Stdin.Stat()
24+
isPipe := false
25+
if (fi.Mode() & os.ModeCharDevice) == 0 {
26+
isPipe = true
27+
}
28+
29+
showQuery := flag.Bool("query", false, "show the query")
30+
minDur := flag.Duration("dur", 0, "only show queries which took longer than this duration, e.g. 10s")
31+
utc := flag.Bool("utc", false, "show timestamp in UTC time")
32+
flag.Parse()
33+
34+
// use tabwriter to format the output spaced out
35+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
36+
37+
// headings
38+
if *showQuery {
39+
fmt.Fprintf(w, "Timestamp\tTraceID\tLength\tDuration\tStatus\tPath\tQuery\n")
40+
} else {
41+
fmt.Fprintf(w, "Timestamp\tTraceID\tLength\tDuration\tStatus\tPath\n")
42+
}
43+
44+
scanner := bufio.NewScanner(os.Stdin)
45+
for scanner.Scan() {
46+
show := false
47+
var qt time.Time
48+
var dur, len time.Duration
49+
var status, path, query, trace string
50+
var err error
51+
52+
line := scanner.Text()
53+
data := rex.FindAllStringSubmatch(line, -1)
54+
55+
for _, d := range data {
56+
switch d[1] {
57+
case "traceID":
58+
trace = d[2]
59+
case "ts":
60+
qt, err = time.Parse(layout, d[2])
61+
if err != nil {
62+
fmt.Println(err, line)
63+
continue
64+
}
65+
case "msg":
66+
msg := strings.ReplaceAll(d[2], "\"", "")
67+
if !(strings.HasPrefix(msg, "GET /loki/api/") ||
68+
strings.HasPrefix(msg, "GET /api/prom")) {
69+
continue
70+
}
71+
72+
show = true
73+
74+
parts := strings.Split(msg, " ")
75+
u, err := url.Parse(parts[1])
76+
if err != nil {
77+
fmt.Println(err, line)
78+
continue
79+
}
80+
vals, err := url.ParseQuery(u.RawQuery)
81+
if err != nil {
82+
fmt.Println(err, line)
83+
continue
84+
}
85+
start := vals.Get("start")
86+
end := vals.Get("end")
87+
88+
if start != "" && end != "" {
89+
var st, et int64
90+
var st_err, et_err error
91+
// First try to parse as unix sec timestamps
92+
st, st_err = strconv.ParseInt(start, 10, 64)
93+
et, et_err = strconv.ParseInt(end, 10, 64)
94+
if st_err != nil || et_err != nil {
95+
//Next try to parse as time/date
96+
var st, et time.Time
97+
st, st_err = time.Parse(layout, start)
98+
et, et_err = time.Parse(layout, end)
99+
if st_err != nil || et_err != nil {
100+
fmt.Println(err, line)
101+
continue
102+
}
103+
len = et.Sub(st)
104+
} else {
105+
// Loki queries are nanosecond, simple check to see if it's a second or nanosecond timestamp
106+
if st > 9999999999 {
107+
len = time.Unix(0, et).Sub(time.Unix(0, st))
108+
} else {
109+
len = time.Unix(et, 0).Sub(time.Unix(st, 0))
110+
}
111+
112+
}
113+
114+
}
115+
116+
status = parts[2]
117+
path = u.Path
118+
query = vals.Get("query")
119+
120+
dur, err = time.ParseDuration(parts[3])
121+
if err != nil {
122+
fmt.Println(err, line)
123+
continue
124+
}
125+
}
126+
}
127+
128+
if show && dur > *minDur {
129+
var ts string
130+
if *utc {
131+
ts = fmt.Sprint(qt)
132+
} else {
133+
ts = fmt.Sprint(qt.Local())
134+
}
135+
if *showQuery {
136+
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\n", ts, trace, len, dur, status, path, query)
137+
} else {
138+
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\n", ts, trace, len, dur, status, path)
139+
}
140+
//If looking at stdin, flush after every line as someone would only paste one line at a time at the terminal
141+
if !isPipe {
142+
w.Flush()
143+
}
144+
145+
}
146+
show = false
147+
}
148+
149+
fmt.Printf("\n")
150+
w.Flush()
151+
152+
}

0 commit comments

Comments
 (0)