├── .gitignore
├── Dockerfile
├── Grafana-dashboard.json
├── LICENSE
├── Makefile
├── README.md
├── clickhouse
└── clickhouse.go
├── config-sample.yml
├── config
└── config.go
├── glide.yaml
├── go.mod
├── go.sum
├── grafana.png
├── main.go
└── nginx
└── nginx.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Glide packages
2 | vendor/
3 | glide.lock
4 |
5 | # Idea
6 | .idea/
7 |
8 | # logs
9 | logs/*.log
10 |
11 | # Config
12 | config.yml
13 |
14 | nginx-clickhouse
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # build stage
2 | FROM golang:1.17-alpine AS build-env
3 |
4 | WORKDIR /go/src/github.com/mintance/nginx-clickhouse
5 |
6 | ADD . /go/src/github.com/mintance/nginx-clickhouse
7 |
8 | RUN apk update && apk add make g++ git curl
9 | RUN cd /go/src/github.com/mintance/nginx-clickhouse && go get .
10 | RUN cd /go/src/github.com/mintance/nginx-clickhouse && make build
11 |
12 | # final stage
13 | FROM scratch
14 |
15 | COPY --from=build-env /go/src/github.com/mintance/nginx-clickhouse/nginx-clickhouse /
16 | CMD [ "/nginx-clickhouse" ]
17 |
18 |
--------------------------------------------------------------------------------
/Grafana-dashboard.json:
--------------------------------------------------------------------------------
1 | {
2 | "editable": true,
3 | "gnetId": null,
4 | "graphTooltip": 0,
5 | "hideControls": false,
6 | "id": 1,
7 | "links": [],
8 | "refresh": false,
9 | "rows": [
10 | {
11 | "collapse": false,
12 | "height": "250px",
13 | "panels": [
14 | {
15 | "aliasColors": {},
16 | "bars": false,
17 | "dashLength": 10,
18 | "dashes": false,
19 | "datasource": "ClickHouse Main Server",
20 | "fill": 1,
21 | "id": 1,
22 | "legend": {
23 | "avg": true,
24 | "current": true,
25 | "max": true,
26 | "min": true,
27 | "show": true,
28 | "total": false,
29 | "values": true
30 | },
31 | "lines": true,
32 | "linewidth": 1,
33 | "links": [],
34 | "nullPointMode": "null",
35 | "percentage": false,
36 | "pointradius": 5,
37 | "points": false,
38 | "renderer": "flot",
39 | "seriesOverrides": [],
40 | "spaceLength": 10,
41 | "span": 12,
42 | "stack": false,
43 | "steppedLine": false,
44 | "targets": [
45 | {
46 | "database": "metrics",
47 | "dateColDataType": "Date",
48 | "dateLoading": false,
49 | "dateTimeColDataType": "TimeLocal",
50 | "datetimeLoading": false,
51 | "formattedQuery": "$columns(Status, count(*) as c) from metrics.nginx_streaming",
52 | "hide": false,
53 | "interval": "15s",
54 | "intervalFactor": 1,
55 | "query": "$columns(Status, count(*) as c) from metrics.nginx",
56 | "rawQuery": "SELECT t, groupArray((Status, c)) as groupArr FROM ( SELECT (intDiv(toUInt32(TimeLocal), 15) * 15) * 1000 as t, Status, count(*) as c from metrics.nginx WHERE Date >= toDate(1539358595) AND TimeLocal >= toDateTime(1539358595) GROUP BY t, Status ORDER BY t) GROUP BY t ORDER BY t",
57 | "refId": "A",
58 | "resultFormat": "time_series",
59 | "table": "nginx_streaming",
60 | "tableLoading": false
61 | }
62 | ],
63 | "thresholds": [],
64 | "timeFrom": null,
65 | "timeShift": null,
66 | "title": "Streaming API Requests & Response",
67 | "tooltip": {
68 | "shared": true,
69 | "sort": 0,
70 | "value_type": "individual"
71 | },
72 | "type": "graph",
73 | "xaxis": {
74 | "buckets": null,
75 | "mode": "time",
76 | "name": null,
77 | "show": true,
78 | "values": []
79 | },
80 | "yaxes": [
81 | {
82 | "decimals": null,
83 | "format": "short",
84 | "label": "Requests",
85 | "logBase": 1,
86 | "max": null,
87 | "min": null,
88 | "show": true
89 | },
90 | {
91 | "format": "short",
92 | "label": "",
93 | "logBase": 1,
94 | "max": null,
95 | "min": null,
96 | "show": true
97 | }
98 | ]
99 | }
100 | ],
101 | "repeat": null,
102 | "repeatIteration": null,
103 | "repeatRowId": null,
104 | "showTitle": false,
105 | "title": "Dashboard Row",
106 | "titleSize": "h6"
107 | },
108 | {
109 | "collapse": false,
110 | "height": 399,
111 | "panels": [
112 | {
113 | "columns": [],
114 | "datasource": "ClickHouse Main Server",
115 | "fontSize": "100%",
116 | "id": 2,
117 | "links": [],
118 | "pageSize": null,
119 | "scroll": true,
120 | "showHeader": true,
121 | "sort": {
122 | "col": 0,
123 | "desc": true
124 | },
125 | "span": 6,
126 | "styles": [
127 | {
128 | "alias": "Time",
129 | "dateFormat": "YYYY-MM-DD HH:mm:ss",
130 | "pattern": "Time",
131 | "type": "date"
132 | },
133 | {
134 | "alias": "",
135 | "colorMode": null,
136 | "colors": [
137 | "rgba(245, 54, 54, 0.9)",
138 | "rgba(237, 129, 40, 0.89)",
139 | "rgba(50, 172, 45, 0.97)"
140 | ],
141 | "decimals": 2,
142 | "pattern": "/.*/",
143 | "thresholds": [],
144 | "type": "number",
145 | "unit": "short"
146 | }
147 | ],
148 | "targets": [
149 | {
150 | "database": "metrics",
151 | "dateColDataType": "Date",
152 | "dateLoading": false,
153 | "dateTimeColDataType": "TimeLocal",
154 | "datetimeLoading": false,
155 | "formattedQuery": "$columns(Status, count(*) as c) from metrics.nginx_streaming",
156 | "hide": false,
157 | "interval": "3600s",
158 | "intervalFactor": 1,
159 | "query": "$columns(Status, count(*) as c) from metrics.nginx",
160 | "rawQuery": "SELECT t, groupArray((Status, c)) as groupArr FROM ( SELECT (intDiv(toUInt32(TimeLocal), 3600) * 3600) * 1000 as t, Status, count(*) as c from metrics.nginx WHERE Date >= toDate(1539358595) AND TimeLocal >= toDateTime(1539358595) GROUP BY t, Status ORDER BY t) GROUP BY t ORDER BY t",
161 | "refId": "A",
162 | "resultFormat": "time_series",
163 | "table": "nginx_streaming",
164 | "tableLoading": false
165 | }
166 | ],
167 | "title": "Requests Stats Table",
168 | "transform": "timeseries_to_columns",
169 | "type": "table"
170 | },
171 | {
172 | "aliasColors": {},
173 | "bars": false,
174 | "dashLength": 10,
175 | "dashes": false,
176 | "datasource": "ClickHouse Main Server",
177 | "fill": 1,
178 | "id": 3,
179 | "legend": {
180 | "avg": false,
181 | "current": false,
182 | "max": false,
183 | "min": false,
184 | "show": true,
185 | "total": false,
186 | "values": false
187 | },
188 | "lines": true,
189 | "linewidth": 1,
190 | "links": [],
191 | "nullPointMode": "null",
192 | "percentage": false,
193 | "pointradius": 5,
194 | "points": false,
195 | "renderer": "flot",
196 | "seriesOverrides": [],
197 | "spaceLength": 10,
198 | "span": 6,
199 | "stack": false,
200 | "steppedLine": false,
201 | "targets": [
202 | {
203 | "database": "metrics",
204 | "dateColDataType": "Date",
205 | "dateLoading": false,
206 | "dateTimeColDataType": "TimeLocal",
207 | "datetimeLoading": false,
208 | "formattedQuery": "$rate(avg(RequestTime) as RequestTime) FROM metrics.nginx_streaming",
209 | "hide": false,
210 | "intervalFactor": 1,
211 | "query": "$rate(avg(RequestTime) as RequestTime) FROM metrics.nginx",
212 | "rawQuery": "SELECT t, RequestTime/runningDifference(t/1000) RequestTimeRate FROM ( SELECT (intDiv(toUInt32(TimeLocal), 120) * 120) * 1000 as t, avg(RequestTime) as RequestTime FROM metrics.nginx WHERE Date >= toDate(1539358595) AND TimeLocal >= toDateTime(1539358595) GROUP BY t ORDER BY t)",
213 | "refId": "A",
214 | "resultFormat": "time_series",
215 | "table": "nginx_streaming",
216 | "tableLoading": false
217 | },
218 | {
219 | "database": "metrics",
220 | "dateColDataType": "Date",
221 | "dateLoading": false,
222 | "dateTimeColDataType": "TimeLocal",
223 | "datetimeLoading": false,
224 | "formattedQuery": "$rate(avg(UpstreamResponseTime) as UpstreamResponseTime) FROM metrics.nginx_streaming",
225 | "hide": false,
226 | "intervalFactor": 1,
227 | "query": "$rate(avg(UpstreamResponseTime) as UpstreamResponseTime) FROM metrics.nginx",
228 | "rawQuery": "SELECT t, UpstreamResponseTime/runningDifference(t/1000) UpstreamResponseTimeRate FROM ( SELECT (intDiv(toUInt32(TimeLocal), 120) * 120) * 1000 as t, avg(UpstreamResponseTime) as UpstreamResponseTime FROM metrics.nginx WHERE Date >= toDate(1539358595) AND TimeLocal >= toDateTime(1539358595) GROUP BY t ORDER BY t)",
229 | "refId": "B",
230 | "resultFormat": "time_series",
231 | "table": "nginx_streaming",
232 | "tableLoading": false
233 | },
234 | {
235 | "database": "metrics",
236 | "dateColDataType": "Date",
237 | "dateLoading": false,
238 | "dateTimeColDataType": "TimeLocal",
239 | "datetimeLoading": false,
240 | "formattedQuery": "$rate(avg(UpstreamConnectTime) as UpstreamConnectTime) FROM metrics.nginx_streaming",
241 | "hide": false,
242 | "intervalFactor": 1,
243 | "query": "$rate(avg(UpstreamConnectTime) as UpstreamConnectTime) FROM metrics.nginx",
244 | "rawQuery": "SELECT t, UpstreamConnectTime/runningDifference(t/1000) UpstreamConnectTimeRate FROM ( SELECT (intDiv(toUInt32(TimeLocal), 120) * 120) * 1000 as t, avg(UpstreamConnectTime) as UpstreamConnectTime FROM metrics.nginx WHERE Date >= toDate(1539358595) AND TimeLocal >= toDateTime(1539358595) GROUP BY t ORDER BY t)",
245 | "refId": "C",
246 | "resultFormat": "time_series",
247 | "table": "nginx_streaming",
248 | "tableLoading": false
249 | }
250 | ],
251 | "thresholds": [],
252 | "timeFrom": null,
253 | "timeShift": null,
254 | "title": "Response Timing",
255 | "tooltip": {
256 | "shared": true,
257 | "sort": 0,
258 | "value_type": "individual"
259 | },
260 | "type": "graph",
261 | "xaxis": {
262 | "buckets": null,
263 | "mode": "time",
264 | "name": null,
265 | "show": true,
266 | "values": []
267 | },
268 | "yaxes": [
269 | {
270 | "format": "short",
271 | "label": null,
272 | "logBase": 1,
273 | "max": null,
274 | "min": null,
275 | "show": true
276 | },
277 | {
278 | "format": "short",
279 | "label": null,
280 | "logBase": 1,
281 | "max": null,
282 | "min": null,
283 | "show": true
284 | }
285 | ]
286 | }
287 | ],
288 | "repeat": null,
289 | "repeatIteration": null,
290 | "repeatRowId": null,
291 | "showTitle": false,
292 | "title": "Dashboard Row",
293 | "titleSize": "h6"
294 | },
295 | {
296 | "collapse": false,
297 | "height": 250,
298 | "panels": [
299 | {
300 | "cacheTimeout": null,
301 | "colorBackground": false,
302 | "colorValue": false,
303 | "colors": [
304 | "rgba(245, 54, 54, 0.9)",
305 | "rgba(237, 129, 40, 0.89)",
306 | "rgba(50, 172, 45, 0.97)"
307 | ],
308 | "datasource": "ClickHouse Main Server",
309 | "format": "none",
310 | "gauge": {
311 | "maxValue": 100,
312 | "minValue": 0,
313 | "show": false,
314 | "thresholdLabels": false,
315 | "thresholdMarkers": true
316 | },
317 | "id": 4,
318 | "interval": null,
319 | "links": [],
320 | "mappingType": 1,
321 | "mappingTypes": [
322 | {
323 | "name": "value to text",
324 | "value": 1
325 | },
326 | {
327 | "name": "range to text",
328 | "value": 2
329 | }
330 | ],
331 | "maxDataPoints": 100,
332 | "nullPointMode": "connected",
333 | "nullText": null,
334 | "postfix": "",
335 | "postfixFontSize": "50%",
336 | "prefix": "",
337 | "prefixFontSize": "50%",
338 | "rangeMaps": [
339 | {
340 | "from": "null",
341 | "text": "N/A",
342 | "to": "null"
343 | }
344 | ],
345 | "span": 2,
346 | "sparkline": {
347 | "fillColor": "rgba(31, 118, 189, 0.18)",
348 | "full": false,
349 | "lineColor": "rgb(31, 120, 193)",
350 | "show": false
351 | },
352 | "tableColumn": "",
353 | "targets": [
354 | {
355 | "database": "metrics",
356 | "dateColDataType": "Date",
357 | "dateLoading": false,
358 | "dateTimeColDataType": "TimeLocal",
359 | "datetimeLoading": false,
360 | "formattedQuery": "SELECT
1 as t,
count(*) as c
FROM $table
WHERE $timeFilter GROUP BY t",
361 | "intervalFactor": 1,
362 | "query": "SELECT\n 1 as t,\n count(*) as c\n FROM $table\n WHERE $timeFilter GROUP BY t",
363 | "rawQuery": "SELECT 1 as t, count(*) as c FROM metrics.nginx WHERE Date >= toDate(1539358595) AND TimeLocal >= toDateTime(1539358595) GROUP BY t",
364 | "refId": "A",
365 | "resultFormat": "time_series",
366 | "table": "nginx_streaming",
367 | "tableLoading": false
368 | }
369 | ],
370 | "thresholds": "",
371 | "title": "Total Requests",
372 | "type": "singlestat",
373 | "valueFontSize": "80%",
374 | "valueMaps": [
375 | {
376 | "op": "=",
377 | "text": "N/A",
378 | "value": "null"
379 | }
380 | ],
381 | "valueName": "avg"
382 | },
383 | {
384 | "cacheTimeout": null,
385 | "colorBackground": false,
386 | "colorValue": false,
387 | "colors": [
388 | "rgba(245, 54, 54, 0.9)",
389 | "rgba(237, 129, 40, 0.89)",
390 | "rgba(50, 172, 45, 0.97)"
391 | ],
392 | "datasource": "ClickHouse Main Server",
393 | "format": "ops",
394 | "gauge": {
395 | "maxValue": 100,
396 | "minValue": 0,
397 | "show": false,
398 | "thresholdLabels": false,
399 | "thresholdMarkers": true
400 | },
401 | "id": 5,
402 | "interval": null,
403 | "links": [],
404 | "mappingType": 1,
405 | "mappingTypes": [
406 | {
407 | "name": "value to text",
408 | "value": 1
409 | },
410 | {
411 | "name": "range to text",
412 | "value": 2
413 | }
414 | ],
415 | "maxDataPoints": 100,
416 | "nullPointMode": "connected",
417 | "nullText": null,
418 | "postfix": "",
419 | "postfixFontSize": "50%",
420 | "prefix": "",
421 | "prefixFontSize": "50%",
422 | "rangeMaps": [
423 | {
424 | "from": "null",
425 | "text": "N/A",
426 | "to": "null"
427 | }
428 | ],
429 | "span": 2,
430 | "sparkline": {
431 | "fillColor": "rgba(31, 118, 189, 0.18)",
432 | "full": false,
433 | "lineColor": "rgb(31, 120, 193)",
434 | "show": false
435 | },
436 | "tableColumn": "",
437 | "targets": [
438 | {
439 | "database": "metrics",
440 | "dateColDataType": "Date",
441 | "dateLoading": false,
442 | "dateTimeColDataType": "TimeLocal",
443 | "datetimeLoading": false,
444 | "formattedQuery": "SELECT
1 as t,
count(*) / 3600*24 as c
FROM $table
WHERE $timeFilter GROUP BY t",
445 | "intervalFactor": 1,
446 | "query": "SELECT\n 1 as t,\n count(*) / 3600*24 as c\n FROM $table\n WHERE $timeFilter GROUP BY t",
447 | "rawQuery": "SELECT 1 as t, count(*) / 3600*24 as c FROM metrics.nginx WHERE Date >= toDate(1539358595) AND TimeLocal >= toDateTime(1539358595) GROUP BY t",
448 | "refId": "A",
449 | "resultFormat": "time_series",
450 | "table": "nginx_streaming",
451 | "tableLoading": false
452 | }
453 | ],
454 | "thresholds": "",
455 | "title": "RPS",
456 | "type": "singlestat",
457 | "valueFontSize": "80%",
458 | "valueMaps": [
459 | {
460 | "op": "=",
461 | "text": "N/A",
462 | "value": "null"
463 | }
464 | ],
465 | "valueName": "avg"
466 | },
467 | {
468 | "cacheTimeout": null,
469 | "colorBackground": true,
470 | "colorValue": false,
471 | "colors": [
472 | "rgba(50, 172, 45, 0.97)",
473 | "rgba(237, 129, 40, 0.89)",
474 | "rgba(245, 54, 54, 0.9)"
475 | ],
476 | "datasource": "ClickHouse Main Server",
477 | "format": "none",
478 | "gauge": {
479 | "maxValue": 100,
480 | "minValue": 0,
481 | "show": false,
482 | "thresholdLabels": false,
483 | "thresholdMarkers": true
484 | },
485 | "id": 6,
486 | "interval": null,
487 | "links": [],
488 | "mappingType": 1,
489 | "mappingTypes": [
490 | {
491 | "name": "value to text",
492 | "value": 1
493 | },
494 | {
495 | "name": "range to text",
496 | "value": 2
497 | }
498 | ],
499 | "maxDataPoints": 100,
500 | "nullPointMode": "connected",
501 | "nullText": null,
502 | "postfix": "",
503 | "postfixFontSize": "50%",
504 | "prefix": "",
505 | "prefixFontSize": "50%",
506 | "rangeMaps": [
507 | {
508 | "from": "null",
509 | "text": "N/A",
510 | "to": "null"
511 | }
512 | ],
513 | "span": 2,
514 | "sparkline": {
515 | "fillColor": "rgba(31, 118, 189, 0.18)",
516 | "full": false,
517 | "lineColor": "rgb(31, 120, 193)",
518 | "show": false
519 | },
520 | "tableColumn": "",
521 | "targets": [
522 | {
523 | "database": "metrics",
524 | "dateColDataType": "Date",
525 | "dateLoading": false,
526 | "dateTimeColDataType": "TimeLocal",
527 | "datetimeLoading": false,
528 | "formattedQuery": "SELECT
1 as t,
count(*) as c
FROM $table
WHERE $timeFilter AND Status NOT IN (200, 201, 401) GROUP BY t",
529 | "intervalFactor": 1,
530 | "query": "SELECT\n 1 as t,\n count(*) as c\n FROM $table\n WHERE $timeFilter AND Status NOT IN (200, 201, 401) GROUP BY t",
531 | "rawQuery": "SELECT 1 as t, count(*) as c FROM metrics.nginx WHERE Date >= toDate(1539358595) AND TimeLocal >= toDateTime(1539358595) AND Status NOT IN (200, 201, 401) GROUP BY t",
532 | "refId": "A",
533 | "resultFormat": "time_series",
534 | "table": "nginx_streaming",
535 | "tableLoading": false
536 | }
537 | ],
538 | "thresholds": "10,20,50",
539 | "title": "Failed Requests",
540 | "type": "singlestat",
541 | "valueFontSize": "80%",
542 | "valueMaps": [
543 | {
544 | "op": "=",
545 | "text": "N/A",
546 | "value": "null"
547 | }
548 | ],
549 | "valueName": "avg"
550 | },
551 | {
552 | "cacheTimeout": null,
553 | "colorBackground": true,
554 | "colorValue": false,
555 | "colors": [
556 | "rgba(50, 172, 45, 0.97)",
557 | "rgba(237, 129, 40, 0.89)",
558 | "rgba(245, 54, 54, 0.9)"
559 | ],
560 | "datasource": "ClickHouse Main Server",
561 | "decimals": null,
562 | "format": "none",
563 | "gauge": {
564 | "maxValue": 100,
565 | "minValue": 0,
566 | "show": false,
567 | "thresholdLabels": false,
568 | "thresholdMarkers": true
569 | },
570 | "id": 9,
571 | "interval": null,
572 | "links": [],
573 | "mappingType": 1,
574 | "mappingTypes": [
575 | {
576 | "name": "value to text",
577 | "value": 1
578 | },
579 | {
580 | "name": "range to text",
581 | "value": 2
582 | }
583 | ],
584 | "maxDataPoints": 100,
585 | "nullPointMode": "connected",
586 | "nullText": null,
587 | "postfix": "%",
588 | "postfixFontSize": "80%",
589 | "prefix": "",
590 | "prefixFontSize": "50%",
591 | "rangeMaps": [
592 | {
593 | "from": "null",
594 | "text": "N/A",
595 | "to": "null"
596 | }
597 | ],
598 | "span": 2,
599 | "sparkline": {
600 | "fillColor": "rgba(31, 118, 189, 0.18)",
601 | "full": false,
602 | "lineColor": "rgb(31, 120, 193)",
603 | "show": false
604 | },
605 | "tableColumn": "",
606 | "targets": [
607 | {
608 | "database": "metrics",
609 | "dateColDataType": "Date",
610 | "dateLoading": false,
611 | "dateTimeColDataType": "TimeLocal",
612 | "datetimeLoading": false,
613 | "formattedQuery": "SELECT
1 as t, (sum(Status = 500 or Status = 499)/sum(Status = 200 or Status = 201 or Status = 401))*100 FROM $table
WHERE $timeFilter GROUP BY t",
614 | "intervalFactor": 1,
615 | "query": "SELECT\n 1 as t, (sum(Status = 500 or Status = 499)/sum(Status = 200 or Status = 201 or Status = 401))*100 FROM $table\n WHERE $timeFilter GROUP BY t",
616 | "rawQuery": "SELECT 1 as t, (sum(Status = 500 or Status = 499)/sum(Status = 200 or Status = 201 or Status = 401))*100 FROM metrics.nginx WHERE Date >= toDate(1539358595) AND TimeLocal >= toDateTime(1539358595) GROUP BY t",
617 | "refId": "A",
618 | "resultFormat": "time_series",
619 | "table": "nginx_streaming",
620 | "tableLoading": false
621 | }
622 | ],
623 | "thresholds": "1,5",
624 | "title": "Failing Prcent",
625 | "type": "singlestat",
626 | "valueFontSize": "80%",
627 | "valueMaps": [
628 | {
629 | "op": "=",
630 | "text": "N/A",
631 | "value": "null"
632 | }
633 | ],
634 | "valueName": "current"
635 | },
636 | {
637 | "cacheTimeout": null,
638 | "colorBackground": true,
639 | "colorValue": false,
640 | "colors": [
641 | "rgba(245, 54, 54, 0.9)",
642 | "rgba(237, 129, 40, 0.89)",
643 | "rgba(50, 172, 45, 0.97)"
644 | ],
645 | "datasource": "ClickHouse Main Server",
646 | "format": "s",
647 | "gauge": {
648 | "maxValue": 100,
649 | "minValue": 0,
650 | "show": false,
651 | "thresholdLabels": false,
652 | "thresholdMarkers": true
653 | },
654 | "id": 7,
655 | "interval": null,
656 | "links": [],
657 | "mappingType": 1,
658 | "mappingTypes": [
659 | {
660 | "name": "value to text",
661 | "value": 1
662 | },
663 | {
664 | "name": "range to text",
665 | "value": 2
666 | }
667 | ],
668 | "maxDataPoints": 100,
669 | "nullPointMode": "connected",
670 | "nullText": null,
671 | "postfix": "",
672 | "postfixFontSize": "50%",
673 | "prefix": "",
674 | "prefixFontSize": "50%",
675 | "rangeMaps": [
676 | {
677 | "from": "null",
678 | "text": "N/A",
679 | "to": "null"
680 | }
681 | ],
682 | "span": 2,
683 | "sparkline": {
684 | "fillColor": "rgba(31, 118, 189, 0.18)",
685 | "full": false,
686 | "lineColor": "rgb(31, 120, 193)",
687 | "show": false
688 | },
689 | "tableColumn": "",
690 | "targets": [
691 | {
692 | "database": "metrics",
693 | "dateColDataType": "Date",
694 | "dateLoading": false,
695 | "dateTimeColDataType": "TimeLocal",
696 | "datetimeLoading": false,
697 | "formattedQuery": "SELECT
1, avg(RequestTime) FROM $table
WHERE $timeFilter GROUP BY 1",
698 | "intervalFactor": 1,
699 | "query": "SELECT\n 1, avg(RequestTime) FROM $table\n WHERE $timeFilter GROUP BY 1",
700 | "rawQuery": "SELECT 1, avg(RequestTime) FROM metrics.nginx WHERE Date >= toDate(1539358595) AND TimeLocal >= toDateTime(1539358595) GROUP BY 1",
701 | "refId": "A",
702 | "resultFormat": "time_series",
703 | "table": "nginx_streaming",
704 | "tableLoading": false
705 | }
706 | ],
707 | "thresholds": "0.02,0.05,0.1",
708 | "title": "Avg Response Time",
709 | "type": "singlestat",
710 | "valueFontSize": "80%",
711 | "valueMaps": [
712 | {
713 | "op": "=",
714 | "text": "N/A",
715 | "value": "null"
716 | }
717 | ],
718 | "valueName": "avg"
719 | },
720 | {
721 | "cacheTimeout": null,
722 | "colorBackground": true,
723 | "colorValue": false,
724 | "colors": [
725 | "rgba(50, 172, 45, 0.97)",
726 | "rgba(237, 129, 40, 0.89)",
727 | "rgba(245, 54, 54, 0.9)"
728 | ],
729 | "datasource": "ClickHouse Main Server",
730 | "decimals": null,
731 | "format": "s",
732 | "gauge": {
733 | "maxValue": 100,
734 | "minValue": 0,
735 | "show": false,
736 | "thresholdLabels": false,
737 | "thresholdMarkers": true
738 | },
739 | "id": 8,
740 | "interval": null,
741 | "links": [],
742 | "mappingType": 1,
743 | "mappingTypes": [
744 | {
745 | "name": "value to text",
746 | "value": 1
747 | },
748 | {
749 | "name": "range to text",
750 | "value": 2
751 | }
752 | ],
753 | "maxDataPoints": 100,
754 | "nullPointMode": "connected",
755 | "nullText": null,
756 | "postfix": "",
757 | "postfixFontSize": "70%",
758 | "prefix": "",
759 | "prefixFontSize": "50%",
760 | "rangeMaps": [
761 | {
762 | "from": "null",
763 | "text": "N/A",
764 | "to": "null"
765 | }
766 | ],
767 | "span": 2,
768 | "sparkline": {
769 | "fillColor": "rgba(31, 118, 189, 0.18)",
770 | "full": false,
771 | "lineColor": "rgb(31, 120, 193)",
772 | "show": false
773 | },
774 | "tableColumn": "",
775 | "targets": [
776 | {
777 | "database": "metrics",
778 | "dateColDataType": "Date",
779 | "dateLoading": false,
780 | "dateTimeColDataType": "TimeLocal",
781 | "datetimeLoading": false,
782 | "formattedQuery": "SELECT
1 as t, max(RequestTime) as c
FROM $table
WHERE $timeFilter GROUP BY t",
783 | "intervalFactor": 1,
784 | "query": "SELECT\n 1 as t, max(RequestTime) as c\n FROM $table\n WHERE $timeFilter GROUP BY t",
785 | "rawQuery": "SELECT 1 as t, max(RequestTime) as c FROM metrics.nginx WHERE Date >= toDate(1539358595) AND TimeLocal >= toDateTime(1539358595) GROUP BY t",
786 | "refId": "A",
787 | "resultFormat": "time_series",
788 | "table": "nginx_streaming",
789 | "tableLoading": false
790 | }
791 | ],
792 | "thresholds": "0.2,0.5,1",
793 | "title": "Max Response Time",
794 | "type": "singlestat",
795 | "valueFontSize": "80%",
796 | "valueMaps": [
797 | {
798 | "op": "=",
799 | "text": "N/A",
800 | "value": "null"
801 | }
802 | ],
803 | "valueName": "current"
804 | }
805 | ],
806 | "repeat": null,
807 | "repeatIteration": null,
808 | "repeatRowId": null,
809 | "showTitle": false,
810 | "title": "Dashboard Row",
811 | "titleSize": "h6"
812 | },
813 | {
814 | "collapse": false,
815 | "height": "450",
816 | "panels": [
817 | {
818 | "columns": [],
819 | "datasource": "ClickHouse Main Server",
820 | "fontSize": "100%",
821 | "id": 11,
822 | "links": [],
823 | "pageSize": null,
824 | "scroll": true,
825 | "showHeader": true,
826 | "sort": {
827 | "col": 0,
828 | "desc": true
829 | },
830 | "span": 8,
831 | "styles": [
832 | {
833 | "alias": "Time",
834 | "dateFormat": "HH:mm:ss",
835 | "pattern": "Time",
836 | "type": "date"
837 | },
838 | {
839 | "alias": "",
840 | "colorMode": null,
841 | "colors": [
842 | "rgba(245, 54, 54, 0.9)",
843 | "rgba(237, 129, 40, 0.89)",
844 | "rgba(50, 172, 45, 0.97)"
845 | ],
846 | "dateFormat": "YYYY-MM-DD HH:mm:ss",
847 | "decimals": 0,
848 | "pattern": "Status",
849 | "thresholds": [],
850 | "type": "number",
851 | "unit": "none"
852 | },
853 | {
854 | "alias": "Response Time",
855 | "colorMode": null,
856 | "colors": [
857 | "rgba(50, 172, 45, 0.97)",
858 | "rgba(237, 129, 40, 0.89)",
859 | "rgba(245, 54, 54, 0.9)"
860 | ],
861 | "dateFormat": "YYYY-MM-DD HH:mm:ss",
862 | "decimals": 2,
863 | "pattern": "RequestTime",
864 | "thresholds": [
865 | "50",
866 | "70",
867 | "100"
868 | ],
869 | "type": "number",
870 | "unit": "dtdurations"
871 | },
872 | {
873 | "alias": "",
874 | "colorMode": null,
875 | "colors": [
876 | "rgba(245, 54, 54, 0.9)",
877 | "rgba(237, 129, 40, 0.89)",
878 | "rgba(50, 172, 45, 0.97)"
879 | ],
880 | "decimals": 2,
881 | "pattern": "/.*/",
882 | "thresholds": [],
883 | "type": "number",
884 | "unit": "short"
885 | }
886 | ],
887 | "targets": [
888 | {
889 | "database": "metrics",
890 | "dateColDataType": "Date",
891 | "dateLoading": false,
892 | "dateTimeColDataType": "TimeLocal",
893 | "datetimeLoading": false,
894 | "formattedQuery": "SELECT TimeLocal as t, Status, RequestMethod, splitByChar(' ', Request)[2] as Request, RequestTime FROM $table WHERE $timeFilter AND Status NOT IN (200, 201) AND RequestMethod != 'OPTIONS' ORDER BY t",
895 | "intervalFactor": 1,
896 | "query": "SELECT TimeLocal as t, Status, RequestMethod, splitByChar(' ', Request)[2] as Request, RequestTime FROM $table WHERE $timeFilter AND Status NOT IN (200, 201) AND RequestMethod != 'OPTIONS' ORDER BY t",
897 | "rawQuery": "SELECT TimeLocal as t, Status, RequestMethod, splitByChar(' ', Request)[2] as Request, RequestTime FROM metrics.nginx WHERE Date >= toDate(1539358595) AND TimeLocal >= toDateTime(1539358595) AND Status NOT IN (200, 201) AND RequestMethod != 'OPTIONS' ORDER BY t",
898 | "refId": "A",
899 | "resultFormat": "time_series",
900 | "table": "nginx_streaming",
901 | "tableLoading": false
902 | }
903 | ],
904 | "title": "Error Requests",
905 | "transform": "timeseries_to_columns",
906 | "type": "table"
907 | }
908 | ],
909 | "repeat": null,
910 | "repeatIteration": null,
911 | "repeatRowId": null,
912 | "showTitle": false,
913 | "title": "Dashboard Row",
914 | "titleSize": "h6"
915 | }
916 | ],
917 | "schemaVersion": 14,
918 | "style": "dark",
919 | "tags": [],
920 | "templating": {
921 | "list": []
922 | },
923 | "time": {
924 | "from": "now-24h",
925 | "to": "now"
926 | },
927 | "timepicker": {
928 | "refresh_intervals": [
929 | "5s",
930 | "10s",
931 | "30s",
932 | "1m",
933 | "5m",
934 | "15m",
935 | "30m",
936 | "1h",
937 | "2h",
938 | "1d"
939 | ],
940 | "time_options": [
941 | "5m",
942 | "15m",
943 | "1h",
944 | "6h",
945 | "12h",
946 | "24h",
947 | "2d",
948 | "7d",
949 | "30d"
950 | ]
951 | },
952 | "timezone": "",
953 | "title": "Streaming API",
954 | "version": 17
955 | }
956 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: dependencies
2 | dependencies:
3 | echo "Installing dependencies"
4 | glide install
5 |
6 | .PHONY: code-quality
7 | code-quality:
8 | gometalinter --vendor --tests --skip=mock --exclude='_gen.go' --disable=gotype --disable=errcheck --disable=gas --disable=dupl --deadline=1500s --checkstyle --sort=linter ./... > static-analysis.xml
9 |
10 | install-helpers:
11 | echo "Installing GoMetaLinter"
12 | go get -u github.com/alecthomas/gometalinter
13 | echo "Installing linters"
14 | gometalinter --install
15 | echo "Installing Glide"
16 | curl https://glide.sh/get | sh
17 |
18 | build:
19 | CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o nginx-clickhouse .
20 |
21 | docker:
22 | docker build --rm --no-cache=true -t mintance/nginx-clickhouse -f Dockerfile .
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nginx-clickhouse [](https://twitter.com/intent/tweet?text=Simple%20NGINX%20logs%20parser%20and%20transporter%20to%20ClickHouse%20database.%20&url=https://github.com/mintance/nginx-clickhouse&hashtags=nginx,clickhouse,golang)
2 |
3 | [](https://github.com/mintance/nginx-clickhouse/blob/master/LICENSE)
4 | 
5 | [](https://hub.docker.com/r/mintance/nginx-clickhouse/)
6 | [](https://hub.docker.com/r/mintance/nginx-clickhouse/)
7 | [](https://hub.docker.com/r/mintance/nginx-clickhouse/)
8 | [](https://github.com/mintance/nginx-clickhouse/issues)
9 |
10 | Simple nginx logs parser & transporter to ClickHouse database.
11 |
12 | ### How to build from sources
13 |
14 | #### 1. Install helpers
15 |
16 | ```sh
17 | make install-helpers
18 | ```
19 |
20 | #### 2. Install dependencies
21 |
22 | ```sh
23 | make dependencies
24 | ```
25 |
26 | #### 3. Build binary file
27 |
28 | ```sh
29 | make build
30 | ```
31 |
32 | ### How to build Docker image
33 |
34 | To build image just type this command, and it will compile binary from sources and create Docker image. You don't need to have Go development tools, the [build process will be in Docker](https://medium.com/travis-on-docker/multi-stage-docker-builds-for-creating-tiny-go-images-e0e1867efe5a).
35 |
36 | ```sh
37 | make docker
38 | ```
39 |
40 | ### How to run
41 |
42 | #### 1. Pull image from Docker Hub (or build from sources)
43 |
44 | ```sh
45 | docker pull mintance/nginx-clickhouse
46 | ```
47 |
48 | There are always last stable image, it automatically builds when release created.
49 |
50 | #### 2. Run Docker container
51 |
52 | For this example, we include `/var/log/nginx` directory, where we store our logs, and `config` directory where we store `config.yml` file.
53 |
54 | ```sh
55 | docker run --rm --net=host --name nginx-clickhouse -v /var/log/nginx:/logs -v config:/config -d mintance/nginx-clickhouse
56 | ```
57 |
58 | ### How it works?
59 |
60 | Here are described full setting-up example.
61 |
62 | #### NGINX log format description
63 |
64 | In nginx, there are: [nginx_http_log_module](http://nginx.org/en/docs/http/ngx_http_log_module.html) that writes request logs in the specified format.
65 |
66 | They are defined in `/etc/nginx/nginx.conf` file. For example we create `main` log format.
67 |
68 | ```lua
69 | http {
70 | ...
71 | log_format main '$remote_addr - $remote_user [$time_local] "$request" $status $bytes_sent "$http_referer" "$http_user_agent"';
72 | ...
73 | }
74 | ```
75 |
76 | After defining this, we can use it in our site config `/etc/nginx/sites-enabled/my-site.conf` inside server section:
77 |
78 | ```lua
79 | server {
80 | ...
81 | access_log /var/log/nginx/my-site-access.log main;
82 | ...
83 | }
84 | ```
85 |
86 | Now all what we need, is to create `config.yml` file where we describe our log format, log file path, and ClickHouse credentials. We can also use environment variables for this.
87 |
88 | #### ClickHouse table schema example
89 |
90 | This is table schema for our example.
91 |
92 | ```sql
93 | CREATE TABLE metrics.nginx (
94 | RemoteAddr String,
95 | RemoteUser String,
96 | TimeLocal DateTime,
97 | Date Date DEFAULT toDate(TimeLocal),
98 | Request String,
99 | RequestMethod String,
100 | Status Int32,
101 | BytesSent Int64,
102 | HttpReferer String,
103 | HttpUserAgent String,
104 | RequestTime Float32,
105 | UpstreamConnectTime Float32,
106 | UpstreamHeaderTime Float32,
107 | UpstreamResponseTime Float32,
108 | Https FixedString(2),
109 | ConnectionsWaiting Int64,
110 | ConnectionsActive Int64
111 | ) ENGINE = MergeTree(Date, (Status, Date), 8192)
112 | ```
113 |
114 | #### Config file description
115 |
116 | ##### 1. Log path & flushing interval
117 |
118 | ```yaml
119 | settings:
120 | interval: 5 # in seconds
121 | log_path: /var/log/nginx/my-site-access.log # path to logfile
122 | ```
123 |
124 | ##### 2. ClickHouse credentials and table schema
125 |
126 | ```yaml
127 | clickhouse:
128 | db: metrics # Database name
129 | table: nginx # Table name
130 | host: localhost # ClickHouse host (cluster support will be added later)
131 | port: 8123 # ClicHhouse HTTP port
132 | credentials:
133 | user: default # User name
134 | password: # User password
135 | ```
136 |
137 | Here we describe in key-value format (key - ClickHouse column, value - log variable) relation between column and log variable.
138 |
139 | ```yaml
140 | columns:
141 | RemoteAddr: remote_addr
142 | RemoteUser: remote_user
143 | TimeLocal: time_local
144 | Request: request
145 | Status: status
146 | BytesSent: bytes_sent
147 | HttpReferer: http_referer
148 | HttpUserAgent: http_user_agent
149 | ```
150 |
151 | ##### 3. NGINX log type & format
152 |
153 | In `log_format` - we just copy format from nginx.conf
154 |
155 | ```yaml
156 | nginx:
157 | log_type: main
158 | log_format: $remote_addr - $remote_user [$time_local] "$request" $status $bytes_sent "$http_referer" "$http_user_agent"
159 | ```
160 |
161 | ##### 4. Full config file example
162 |
163 | ```yaml
164 | settings:
165 | interval: 5
166 | log_path: /var/log/nginx/my-site-access.log
167 | seek_from_end: false
168 | clickhouse:
169 | db: metrics
170 | table: nginx
171 | host: localhost
172 | port: 8123
173 | credentials:
174 | user: default
175 | password:
176 | columns:
177 | RemoteAddr: remote_addr
178 | RemoteUser: remote_user
179 | TimeLocal: time_local
180 | Request: request
181 | Status: status
182 | BytesSent: bytes_sent
183 | HttpReferer: http_referer
184 | HttpUserAgent: http_user_agent
185 | nginx:
186 | log_type: main
187 | log_format: $remote_addr - $remote_user [$time_local] "$request" $status $bytes_sent "$http_referer" "$http_user_agent"
188 | ```
189 |
190 | #### Grafana Dashboard
191 |
192 | After all steps you can build your own grafana dashboards.
193 |
194 | 
195 |
196 | 
197 |
--------------------------------------------------------------------------------
/clickhouse/clickhouse.go:
--------------------------------------------------------------------------------
1 | package clickhouse
2 |
3 | import (
4 | "github.com/mintance/go-clickhouse"
5 | "github.com/mintance/nginx-clickhouse/config"
6 | "github.com/mintance/nginx-clickhouse/nginx"
7 | "github.com/satyrius/gonx"
8 | "net/url"
9 | "reflect"
10 | "github.com/sirupsen/logrus"
11 | )
12 |
13 | var clickHouseStorage *clickhouse.Conn
14 |
15 | func Save(config *config.Config, logs []gonx.Entry) error {
16 |
17 | storage, err := getStorage(config)
18 |
19 | if err != nil {
20 | return err
21 | }
22 |
23 | columns := getColumns(config.ClickHouse.Columns)
24 |
25 | rows := buildRows(columns, config.ClickHouse.Columns, logs)
26 |
27 | query, err := clickhouse.BuildMultiInsert(
28 | config.ClickHouse.Db+"."+config.ClickHouse.Table,
29 | columns,
30 | rows,
31 | )
32 |
33 | if err != nil {
34 | return err
35 | }
36 |
37 | return query.Exec(storage)
38 | }
39 |
40 | func getColumns(columns map[string]string) []string {
41 |
42 | keys := reflect.ValueOf(columns).MapKeys()
43 | stringColumns := make([]string, len(keys))
44 |
45 | for i := 0; i < len(keys); i++ {
46 | stringColumns[i] = keys[i].String()
47 | }
48 |
49 | return stringColumns
50 | }
51 |
52 | func buildRows(keys []string, columns map[string]string, data []gonx.Entry) (rows clickhouse.Rows) {
53 |
54 | for _, logEntry := range data {
55 | row := clickhouse.Row{}
56 |
57 | for _, column := range keys {
58 | value, err := logEntry.Field(columns[column])
59 | if err != nil {
60 | logrus.Errorf("error to build rows: %v", err)
61 | }
62 | row = append(row, nginx.ParseField(columns[column], value))
63 | }
64 |
65 | rows = append(rows, row)
66 | }
67 |
68 | return rows
69 | }
70 |
71 | func getStorage(config *config.Config) (*clickhouse.Conn, error) {
72 |
73 | if clickHouseStorage != nil {
74 | return clickHouseStorage, nil
75 | }
76 |
77 | cHTTP := clickhouse.NewHttpTransport()
78 | conn := clickhouse.NewConn(config.ClickHouse.Host+":"+config.ClickHouse.Port, cHTTP)
79 |
80 | params := url.Values{}
81 | params.Add("user", config.ClickHouse.Credentials.User)
82 | params.Add("password", config.ClickHouse.Credentials.Password)
83 | conn.SetParams(params)
84 |
85 | if err := conn.Ping(); err != nil {
86 | return nil, err
87 | }
88 |
89 | return conn, nil
90 | }
91 |
--------------------------------------------------------------------------------
/config-sample.yml:
--------------------------------------------------------------------------------
1 | # Settings
2 | settings:
3 | interval: 5 # in seconds
4 | log_path: logs/test.log # path to logfile
5 | seek_from_end: false # start reading from the lasl line (to prevent duplicates after restart)
6 | # ClickHouse credentials
7 | clickhouse:
8 | db: metrics
9 | table: nginx
10 | host: localhost
11 | port: 8123
12 | credentials:
13 | user: default
14 | password:
15 | columns:
16 | RemoteAddr: remote_addr
17 | RemoteUser: remote_user
18 | TimeLocal: time_local
19 | Request: request
20 | Status: status
21 | BytesSent: bytes_sent
22 | HttpReferer: http_referer
23 | HttpUserAgent: http_user_agent
24 |
25 | # NGINX
26 | nginx:
27 | log_type: main
28 | log_format: $remote_addr - $remote_user [$time_local] "$request" $status $bytes_sent "$http_referer" "$http_user_agent"
29 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "flag"
5 | "io/ioutil"
6 | "os"
7 | "strconv"
8 |
9 | "github.com/sirupsen/logrus"
10 | "gopkg.in/yaml.v2"
11 | )
12 |
13 | type Config struct {
14 | Settings struct {
15 | Interval int `yaml:"interval"`
16 | LogPath string `yaml:"log_path"`
17 | SeekFromEnd bool `yaml:"seek_from_end"`
18 | } `yaml:"settings"`
19 | ClickHouse struct {
20 | Db string `yaml:"db"`
21 | Table string `yaml:"table"`
22 | Host string `yaml:"host"`
23 | Port string `yaml:"port"`
24 | Columns map[string]string `yaml:"columns"`
25 | Credentials struct {
26 | User string `yaml:"user"`
27 | Password string `yaml:"password"`
28 | } `yaml:"credentials"`
29 | } `yaml:"clickhouse"`
30 | Nginx struct {
31 | LogType string `yaml:"log_type"`
32 | LogFormat string `yaml:"log_format"`
33 | }
34 | }
35 |
36 | var configPath string
37 |
38 | var NginxTimeLayout = "02/Jan/2006:15:04:05 -0700"
39 | var CHTimeLayout = "2006-01-02 15:04:05"
40 |
41 | func init() {
42 | flag.StringVar(&configPath, "config_path", "config/config.yml", "Config path.")
43 | flag.Parse()
44 | }
45 |
46 | func Read() *Config {
47 |
48 | config := Config{}
49 |
50 | logrus.Info("Reading config file: " + configPath)
51 |
52 | var data, err = ioutil.ReadFile(configPath)
53 |
54 | if err != nil {
55 | logrus.Fatal("Config open error: ", err)
56 | }
57 |
58 | if err = yaml.Unmarshal(data, &config); err != nil {
59 | logrus.Fatal("Config read & unmarshal error: ", err)
60 | }
61 |
62 | return &config
63 | }
64 |
65 | func (c *Config) SetEnvVariables() {
66 |
67 | // Settings
68 |
69 | if os.Getenv("LOG_PATH") != "" {
70 | c.Settings.LogPath = os.Getenv("LOG_PATH")
71 | }
72 |
73 | if os.Getenv("FLUSH_INTERVAL") != "" {
74 |
75 | var flushInterval, err = strconv.Atoi(os.Getenv("FLUSH_INTERVAL"))
76 |
77 | if err != nil {
78 | logrus.Errorf("error to convert FLUSH_INTERVAL string to int: %v", err)
79 | }
80 |
81 | c.Settings.Interval = flushInterval
82 | }
83 |
84 | // ClickHouse
85 |
86 | if os.Getenv("CLICKHOUSE_HOST") != "" {
87 | c.ClickHouse.Host = os.Getenv("CLICKHOUSE_HOST")
88 | }
89 |
90 | if os.Getenv("CLICKHOUSE_PORT") != "" {
91 | c.ClickHouse.Port = os.Getenv("CLICKHOUSE_PORT")
92 | }
93 |
94 | if os.Getenv("CLICKHOUSE_DB") != "" {
95 | c.ClickHouse.Db = os.Getenv("CLICKHOUSE_DB")
96 | }
97 |
98 | if os.Getenv("CLICKHOUSE_TABLE") != "" {
99 | c.ClickHouse.Table = os.Getenv("CLICKHOUSE_TABLE")
100 | }
101 |
102 | if os.Getenv("CLICKHOUSE_USER") != "" {
103 | c.ClickHouse.Credentials.User = os.Getenv("CLICKHOUSE_USER")
104 | }
105 |
106 | if os.Getenv("CLICKHOUSE_PASSWORD") != "" {
107 | c.ClickHouse.Credentials.Password = os.Getenv("CLICKHOUSE_PASSWORD")
108 | }
109 |
110 | // Nginx
111 |
112 | if os.Getenv("NGINX_LOG_TYPE") != "" {
113 | c.Nginx.LogType = os.Getenv("NGINX_LOG_TYPE")
114 | }
115 |
116 | if os.Getenv("NGINX_LOG_FORMAT") != "" {
117 | c.Nginx.LogFormat = os.Getenv("NGINX_LOG_FORMAT")
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/glide.yaml:
--------------------------------------------------------------------------------
1 | package: mintance/nginx-clickhouse
2 | import:
3 | - package: gopkg.in/yaml.v2
4 | - package: github.com/sirupsen/logrus
5 | version: ~1.0.3
6 | - package: github.com/satyrius/gonx
7 | - package: github.com/mintance/go-clickhouse
8 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mintance/nginx-clickhouse
2 |
3 | go 1.17
4 |
5 | require (
6 | github.com/mintance/go-clickhouse v1.0.2-0.20180104154718-75c62792649d
7 | github.com/papertrail/go-tail v0.0.0-20180509224916-973c153b0431
8 | github.com/prometheus/client_golang v1.12.1
9 | github.com/satyrius/gonx v1.3.1-0.20210630191304-422f42f8923b
10 | github.com/sirupsen/logrus v1.8.1
11 | gopkg.in/yaml.v2 v2.4.0
12 | )
13 |
14 | require (
15 | github.com/beorn7/perks v1.0.1 // indirect
16 | github.com/cespare/xxhash/v2 v2.1.2 // indirect
17 | github.com/fsnotify/fsnotify v1.5.1 // indirect
18 | github.com/golang/protobuf v1.5.2 // indirect
19 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
20 | github.com/prometheus/client_model v0.2.0 // indirect
21 | github.com/prometheus/common v0.32.1 // indirect
22 | github.com/prometheus/procfs v0.7.3 // indirect
23 | github.com/smartystreets/goconvey v1.7.2 // indirect
24 | golang.org/x/sys v0.1.0 // indirect
25 | google.golang.org/protobuf v1.33.0 // indirect
26 | )
27 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
34 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
35 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
36 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
37 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
38 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
39 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
40 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
41 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
42 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
43 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
44 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
45 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
46 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
47 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
48 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
49 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
50 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
51 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
52 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
53 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
54 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
55 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
56 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
57 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
58 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
59 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
60 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
61 | github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
62 | github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
63 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
64 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
65 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
66 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
67 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
68 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
69 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
70 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
71 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
72 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
73 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
74 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
75 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
76 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
77 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
78 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
79 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
80 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
81 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
82 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
83 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
84 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
85 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
86 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
87 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
88 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
89 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
90 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
91 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
92 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
93 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
94 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
95 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
96 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
97 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
98 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
99 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
100 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
101 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
102 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
103 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
104 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
105 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
106 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
107 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
108 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
109 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
110 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
111 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
112 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
113 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
114 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
115 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
116 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
117 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
118 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
119 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
120 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
121 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
122 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
123 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
124 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
125 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
126 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
127 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
128 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
129 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
130 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
131 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
132 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
133 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
134 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
135 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
136 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
137 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
138 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
139 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
140 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
141 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
142 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
143 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
144 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
145 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
146 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
147 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
148 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
149 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
150 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
151 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
152 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
153 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
154 | github.com/mintance/go-clickhouse v1.0.2-0.20180104154718-75c62792649d h1:L1l4wzaV10pnoiJXf7rdKazd8XlzO+tnULbixyqpqTg=
155 | github.com/mintance/go-clickhouse v1.0.2-0.20180104154718-75c62792649d/go.mod h1:qOKYHnwx0JcAsIg7Bauma3iB58DNqTzZ3Ih9Zhvia8k=
156 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
157 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
158 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
159 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
160 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
161 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
162 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
163 | github.com/papertrail/go-tail v0.0.0-20180509224916-973c153b0431 h1:i1egM7gz4bPxLCIwBJOkpk6TqHpjTnL4dE1xdN/4dcs=
164 | github.com/papertrail/go-tail v0.0.0-20180509224916-973c153b0431/go.mod h1:dMID0RaS2a5rhpOjC4RsAKitU6WGgkFBZnPVffL69b8=
165 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
166 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
167 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
168 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
169 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
170 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
171 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
172 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
173 | github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
174 | github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
175 | github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
176 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
177 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
178 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
179 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
180 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
181 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
182 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
183 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
184 | github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
185 | github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
186 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
187 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
188 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
189 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
190 | github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
191 | github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
192 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
193 | github.com/satyrius/gonx v1.3.1-0.20210630191304-422f42f8923b h1:ya8UtEL8HD9bFgiNiF1sqIO+/HiXbr82B2958x3y9MI=
194 | github.com/satyrius/gonx v1.3.1-0.20210630191304-422f42f8923b/go.mod h1:+r8KNe5d2tjkZU+DfhERo0G6KxkGih+1qYF6tqLHwvk=
195 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
196 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
197 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
198 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
199 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
200 | github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
201 | github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
202 | github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
203 | github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
204 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
205 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
206 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
207 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
208 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
209 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
210 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
211 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
212 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
213 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
214 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
215 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
216 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
217 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
218 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
219 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
220 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
221 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
222 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
223 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
224 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
225 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
226 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
227 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
228 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
229 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
230 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
231 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
232 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
233 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
234 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
235 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
236 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
237 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
238 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
239 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
240 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
241 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
242 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
243 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
244 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
245 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
246 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
247 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
248 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
249 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
250 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
251 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
252 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
253 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
254 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
255 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
256 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
257 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
258 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
259 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
260 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
261 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
262 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
263 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
264 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
265 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
266 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
267 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
268 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
269 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
270 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
271 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
272 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
273 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
274 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
275 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
276 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
277 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
278 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
279 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
280 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
281 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
282 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
283 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
284 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
285 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
286 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
287 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
288 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
289 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
290 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
291 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
292 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
293 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
294 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
295 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
296 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
297 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
298 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
299 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
300 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
301 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
302 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
303 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
304 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
305 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
306 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
307 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
308 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
309 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
310 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
311 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
312 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
313 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
314 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
315 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
316 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
317 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
318 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
319 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
320 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
321 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
322 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
323 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
324 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
325 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
326 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
327 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
328 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
329 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
330 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
331 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
332 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
333 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
334 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
335 | golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
336 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
337 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
338 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
339 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
340 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
341 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
342 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
343 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
344 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
345 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
346 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
347 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
348 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
349 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
350 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
351 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
352 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
353 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
354 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
355 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
356 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
357 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
358 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
359 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
360 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
361 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
362 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
363 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
364 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
365 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
366 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
367 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
368 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
369 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
370 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
371 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
372 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
373 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
374 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
375 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
376 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
377 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
378 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
379 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
380 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
381 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
382 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
383 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
384 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
385 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
386 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
387 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
388 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
389 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
390 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
391 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
392 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
393 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
394 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
395 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
396 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
397 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
398 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
399 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
400 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
401 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
402 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
403 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
404 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
405 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
406 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
407 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
408 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
409 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
410 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
411 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
412 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
413 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
414 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
415 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
416 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
417 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
418 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
419 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
420 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
421 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
422 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
423 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
424 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
425 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
426 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
427 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
428 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
429 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
430 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
431 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
432 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
433 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
434 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
435 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
436 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
437 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
438 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
439 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
440 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
441 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
442 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
443 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
444 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
445 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
446 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
447 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
448 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
449 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
450 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
451 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
452 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
453 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
454 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
455 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
456 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
457 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
458 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
459 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
460 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
461 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
462 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
463 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
464 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
465 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
466 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
467 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
468 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
469 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
470 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
471 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
472 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
473 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
474 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
475 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
476 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
477 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
478 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
479 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
480 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
481 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
482 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
483 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
484 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
485 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
486 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
487 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
488 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
489 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
490 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
491 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
492 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
493 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
494 |
--------------------------------------------------------------------------------
/grafana.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mintance/nginx-clickhouse/a98c2b3f9ebf2b88c11c68a6b438b5d6af062496/grafana.png
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "net/http"
6 | "strings"
7 | "sync"
8 | "time"
9 |
10 | "github.com/sirupsen/logrus"
11 | "github.com/mintance/nginx-clickhouse/clickhouse"
12 | configParser "github.com/mintance/nginx-clickhouse/config"
13 | "github.com/mintance/nginx-clickhouse/nginx"
14 | "github.com/papertrail/go-tail/follower"
15 | "github.com/prometheus/client_golang/prometheus"
16 | "github.com/prometheus/client_golang/prometheus/promauto"
17 | "github.com/prometheus/client_golang/prometheus/promhttp"
18 | )
19 |
20 | var (
21 | locker sync.Mutex
22 | logs []string
23 | )
24 |
25 | var (
26 | linesProcessed = promauto.NewCounter(prometheus.CounterOpts{
27 | Name: "nginx_clickhouse_lines_processed_total",
28 | Help: "The total number of processed log lines",
29 | })
30 | linesNotProcessed = promauto.NewCounter(prometheus.CounterOpts{
31 | Name: "nginx_clickhouse_lines_not_processed_total",
32 | Help: "The total number of log lines which was not processed",
33 | })
34 | )
35 |
36 | func main() {
37 |
38 | // Read config & incoming flags
39 | config := configParser.Read()
40 |
41 | // Update config with environment variables if exist
42 | config.SetEnvVariables()
43 |
44 | nginxParser, err := nginx.GetParser(config)
45 |
46 | go func() {
47 | http.Handle("/metrics", promhttp.Handler())
48 | http.ListenAndServe(":2112", nil)
49 | }()
50 |
51 | if err != nil {
52 | logrus.Fatal("Can`t parse nginx log format: ", err)
53 | }
54 |
55 | logs = []string{}
56 |
57 | logrus.Info("Trying to open logfile: " + config.Settings.LogPath)
58 |
59 | whenceSeek := io.SeekStart
60 | if config.Settings.SeekFromEnd {
61 | whenceSeek = io.SeekEnd
62 | }
63 |
64 | t, err := follower.New(config.Settings.LogPath, follower.Config{
65 | Whence: whenceSeek,
66 | Offset: 0,
67 | Reopen: true,
68 | })
69 |
70 | if err != nil {
71 | logrus.Fatal("Can`t tail logfile: ", err)
72 | }
73 |
74 | go func() {
75 | for {
76 | time.Sleep(time.Second * time.Duration(config.Settings.Interval))
77 |
78 | if len(logs) > 0 {
79 |
80 | logrus.Info("Preparing to save ", len(logs), " new log entries.")
81 | locker.Lock()
82 | err := clickhouse.Save(config, nginx.ParseLogs(nginxParser, logs))
83 |
84 | if err != nil {
85 | logrus.Error("Can`t save logs: ", err)
86 | linesNotProcessed.Add(float64(len(logs)))
87 | } else {
88 | logrus.Info("Saved ", len(logs), " new logs.")
89 | linesProcessed.Add(float64(len(logs)))
90 | }
91 |
92 | logs = []string{}
93 | locker.Unlock()
94 | }
95 | }
96 | }()
97 |
98 | // Push new log entries to array
99 | for line := range t.Lines() {
100 | locker.Lock()
101 | logs = append(logs, strings.TrimSpace(line.String()))
102 | locker.Unlock()
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/nginx/nginx.go:
--------------------------------------------------------------------------------
1 | package nginx
2 |
3 | import (
4 | "github.com/mintance/nginx-clickhouse/config"
5 | "github.com/satyrius/gonx"
6 | "io"
7 | "strconv"
8 | "strings"
9 | "time"
10 | "github.com/sirupsen/logrus"
11 | "fmt"
12 | )
13 |
14 | func GetParser(config *config.Config) (*gonx.Parser, error) {
15 |
16 | // Use nginx config file to extract format by the name
17 | nginxConfig := strings.NewReader(fmt.Sprintf("%s%s%s",`
18 | http {
19 | log_format main '`, config.Nginx.LogFormat, `';
20 | }
21 | `))
22 |
23 | return gonx.NewNginxParser(nginxConfig, config.Nginx.LogType)
24 | }
25 |
26 | func ParseField(key string, value string) interface{} {
27 |
28 | switch key {
29 | case "time_local":
30 |
31 | t, err := time.Parse(config.NginxTimeLayout, value)
32 |
33 | if err == nil {
34 | return t.Format(config.CHTimeLayout)
35 | }
36 |
37 | return value
38 |
39 | case "remote_addr", "remote_user", "request", "http_referer", "http_user_agent", "request_method", "https":
40 | return value
41 | case "bytes_sent", "connections_waiting", "connections_active", "status", "connection", "request_length", "body_bytes_sent":
42 | val, err := strconv.Atoi(value)
43 |
44 | if err != nil {
45 | logrus.Error("Error to convert string to int")
46 | }
47 |
48 | return val
49 | case "request_time", "upstream_connect_time", "upstream_header_time", "upstream_response_time", "msec":
50 | val, err := strconv.ParseFloat(value, 32)
51 |
52 | if err != nil {
53 | logrus.Error("Error to convert string to float32")
54 | }
55 |
56 | return val
57 | default:
58 | return value
59 | }
60 |
61 | return value
62 | }
63 |
64 | func ParseLogs(parser *gonx.Parser, logLines []string) []gonx.Entry {
65 |
66 | logReader := strings.NewReader(strings.Join(logLines, "\n"))
67 | reader := gonx.NewParserReader(logReader, parser)
68 |
69 | var logs []gonx.Entry
70 |
71 | for {
72 | rec, err := reader.Read()
73 | if err == io.EOF {
74 | break
75 | } else if err != nil {
76 | panic(err)
77 | }
78 | // Process the record... e.g.
79 | logs = append(logs, *rec)
80 | }
81 |
82 | return logs
83 | }
84 |
--------------------------------------------------------------------------------