From 76df0967917481b11f65cc7ed925240e59498b32 Mon Sep 17 00:00:00 2001 From: Domas Date: Thu, 12 Nov 2020 13:29:43 +0200 Subject: [PATCH] Logging: Log frontend errors (#28073) * basic frontend Sentry integration * backend endpoint to capture sentry events * WIP! * log user email for frontend logs * remove debug logging * lint fixes * Fix type exports & property names Co-authored-by: Arve Knudsen * additional struct naming fix * rename log endpoint, config section & interface * add sentry sample rate to config * refac to use EchoSrv * log user id * backend tests * tests for SentryEchoBackend * sentry echo backend tests * CustomEndpointTransport tests * Update pkg/api/frontend_logging_test.go Co-authored-by: Arve Knudsen * Update conf/defaults.ini Co-authored-by: Arve Knudsen * Update pkg/api/frontend_logging_test.go Co-authored-by: Arve Knudsen * don't export unnecesasrily * update go.sum * get rid of Convey in tests, use stdlib * add sentry config to sample.ini * cleanup to set orig logging handler in test * Apply suggestions from code review Co-authored-by: Arve Knudsen * PR feedback changes * lock sentry version Co-authored-by: Arve Knudsen --- conf/defaults.ini | 13 ++ conf/sample.ini | 13 ++ go.mod | 4 +- go.sum | 83 ++++++++++ package.json | 3 + packages/grafana-data/src/types/config.ts | 13 ++ packages/grafana-runtime/src/config.ts | 6 + .../grafana-runtime/src/services/EchoSrv.ts | 1 + pkg/api/api.go | 3 + pkg/api/dtos/index.go | 3 + pkg/api/frontend_logging.go | 90 +++++++++++ pkg/api/frontend_logging_test.go | 149 ++++++++++++++++++ pkg/api/frontendsettings.go | 1 + pkg/api/index.go | 1 + pkg/setting/setting.go | 4 + pkg/setting/setting_sentry.go | 18 +++ public/app/app.ts | 8 + public/app/core/services/echo/EchoSrv.ts | 5 + .../backends/sentry/SentryBackend.test.ts | 131 +++++++++++++++ .../echo/backends/sentry/SentryBackend.ts | 59 +++++++ .../CustomEndpointTransport.test.ts | 64 ++++++++ .../transports/CustomEndpointTransport.ts | 118 ++++++++++++++ .../sentry/transports/EchoSrvTransport.ts | 14 ++ .../services/echo/backends/sentry/types.ts | 14 ++ yarn.lock | 65 ++++++++ 25 files changed, 880 insertions(+), 3 deletions(-) create mode 100644 pkg/api/frontend_logging.go create mode 100644 pkg/api/frontend_logging_test.go create mode 100644 pkg/setting/setting_sentry.go create mode 100644 public/app/core/services/echo/backends/sentry/SentryBackend.test.ts create mode 100644 public/app/core/services/echo/backends/sentry/SentryBackend.ts create mode 100644 public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.test.ts create mode 100644 public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.ts create mode 100644 public/app/core/services/echo/backends/sentry/transports/EchoSrvTransport.ts create mode 100644 public/app/core/services/echo/backends/sentry/types.ts diff --git a/conf/defaults.ini b/conf/defaults.ini index 8e3c09d78ca..2a5dbf66609 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -560,6 +560,19 @@ facility = # Syslog tag. By default, the process' argv[0] is used. tag = +[log.frontend] +# Should Sentry be initialized +enabled = false + +# Sentry DSN if you wanna send events to Sentry. In this case, set custom_endpoint to empty +sentry_dsn = + +# Custom endpoint to send Sentry events to. If this is configured, DSN will be ignored and events push to this endpoint. Default endpoint will log frontend errors to stdout. +custom_endpoint = /log + +# Rate of events to be reported between 0 (none) and 1 (all), float +sample_rate = 1.0 + #################################### Usage Quotas ######################## [quota] enabled = false diff --git a/conf/sample.ini b/conf/sample.ini index 838cb501e6f..ee5106154f3 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -551,6 +551,19 @@ # Syslog tag. By default, the process' argv[0] is used. ;tag = +[log.frontend] +# Should Sentry be initialized +;enabled = false + +# Sentry DSN if you wanna send events to Sentry. In this case, set custom_endpoint to empty +;sentry_dsn = + +# Custom endpoint to send Sentry events to. If this is configured, DSN will be ignored and events push to this endpoint. Default endpoint will log frontend errors to stdout. +;custom_endpoint = /log + +# Rate of events to be reported between 0 (none) and 1 (all), float +;sample_rate = 1.0 + #################################### Usage Quotas ######################## [quota] ; enabled = false diff --git a/go.mod b/go.mod index 10803268325..e961237b513 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect github.com/fatih/color v1.9.0 github.com/gchaincl/sqlhooks v1.3.0 + github.com/getsentry/sentry-go v0.7.0 github.com/go-macaron/binding v0.0.0-20190806013118-0b4f37bab25b github.com/go-macaron/gzip v0.0.0-20160222043647-cad1c6580a07 github.com/go-sql-driver/mysql v1.5.0 @@ -53,7 +54,6 @@ require ( github.com/jmespath/go-jmespath v0.4.0 github.com/jonboulle/clockwork v0.2.1 // indirect github.com/jung-kurt/gofpdf v1.10.1 - github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/lib/pq v1.3.0 github.com/linkedin/goavro/v2 v2.9.7 github.com/magefile/mage v1.9.0 @@ -80,8 +80,6 @@ require ( github.com/urfave/cli/v2 v2.1.1 github.com/xorcare/pointer v1.1.0 github.com/yudai/gojsondiff v1.0.0 - github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect - github.com/yudai/pp v2.0.1+incompatible // indirect golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a golang.org/x/net v0.0.0-20201022231255-08b38378de70 golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 diff --git a/go.sum b/go.sum index 827670c4d8e..e9e5cab139d 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,7 @@ cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7ni collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= github.com/Azure/azure-sdk-for-go v23.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= @@ -83,6 +84,8 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= +github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -90,6 +93,8 @@ github.com/FZambia/eagle v0.0.1 h1:FN1yTkPihMb5nE8SrlRjoCf7T9H9bTKJFQOm6ach2YU= github.com/FZambia/eagle v0.0.1/go.mod h1:xq6u/JeNZ5/8mrAQ76MMhzNTodASh9FavQlCgg4j48w= github.com/FZambia/sentinel v1.1.0 h1:qrCBfxc8SvJihYNjBWgwUI93ZCvFe/PJIPTHKmlp8a8= github.com/FZambia/sentinel v1.1.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/squirrel v0.0.0-20161115235646-20f192218cf5/go.mod h1:xnKTFzjGUiZtiOagBsfnvomW+nJg2usB1ZpordQWqNM= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= @@ -106,6 +111,7 @@ github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= @@ -113,6 +119,7 @@ github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f h1:HR5nRmUQgX github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f/go.mod h1:f3HiCrHjHBdcm6E83vGaXh1KomZMA2P6aeo3hKx/wg0= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -129,6 +136,7 @@ github.com/apache/arrow/go/arrow v0.0.0-20200629181129-68b1273cbbf7/go.mod h1:QN github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= @@ -153,6 +161,7 @@ github.com/aws/aws-sdk-go v1.33.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZve github.com/aws/aws-sdk-go v1.35.5 h1:doSEOxC0UkirPcle20Rc+1kAhJ4Ip+GSEeZ3nKl7Qlk= github.com/aws/aws-sdk-go v1.35.5/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= @@ -211,9 +220,12 @@ github.com/cockroachdb/datadriven v0.0.0-20190531201743-edce55837238/go.mod h1:z github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -223,6 +235,8 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cortexproject/cortex v0.6.1-0.20200228110116-92ab6cbe0995/go.mod h1:3Xa3DjJxtpXqxcMGdk850lcIRb81M0fyY1MQ6udY134= github.com/cortexproject/cortex v1.2.1-0.20200803161316-7014ff11ed70/go.mod h1:PVPxNLrxKH+yc8asaJOxuz7TiRmMizFfnSMOnRzM6oM= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -251,9 +265,11 @@ github.com/deepmap/oapi-codegen v1.3.11 h1:Nd3tDQfqgquLmCzyRONHzs5SJEwPPoQcFZxT8 github.com/deepmap/oapi-codegen v1.3.11/go.mod h1:suMvK7+rKlx3+tpa8ByptmvoXbAV70wERKTOGH3hLp0= github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec h1:NfhRXXFDPxcF5Cwo06DzeIaE7uuJtAUhsDwH3LNsjos= github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dhui/dktest v0.3.0/go.mod h1:cyzIUfGsBEbZ6BT7tnXqAShHSXCZhSNmFl70sZ7c1yc= github.com/digitalocean/godo v1.37.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU= @@ -279,6 +295,7 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/elastic/go-sysinfo v1.0.1/go.mod h1:O/D5m1VpYLwGjCYzEt63g3Z1uO3jXfwyzzjiW90t8cY= github.com/elastic/go-sysinfo v1.1.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0= github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= @@ -292,6 +309,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -306,10 +324,13 @@ github.com/facebookgo/structtag v0.0.0-20150214074306-217e25fb9691/go.mod h1:sKL github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc= +github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structtag v1.1.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA= github.com/fluent/fluent-bit-go v0.0.0-20190925192703-ea13c021720c/go.mod h1:WQX+afhrekY9rGK+WT4xvKSlzmia9gDoLYu4GGYGASQ= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= @@ -319,17 +340,25 @@ github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03D github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= +github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/gchaincl/sqlhooks v1.3.0 h1:yKPXxW9a5CjXaVf2HkQn6wn7TZARvbAOAelr3H8vK2Y= github.com/gchaincl/sqlhooks v1.3.0/go.mod h1:9BypXnereMT0+Ys8WGWHqzgkkOfHIhyeUCqXC24ra34= github.com/getkin/kin-openapi v0.2.0/go.mod h1:V1z9xl9oF5Wt7v32ne4FmiF1alpS4dM6mNzoywPOXlk= github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw= +github.com/getsentry/sentry-go v0.7.0 h1:MR2yfR4vFfv/2+iBuSnkdQwVg7N9cJzihZ6KJu7srwQ= +github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -348,6 +377,7 @@ github.com/go-macaron/gzip v0.0.0-20160222043647-cad1c6580a07 h1:YSIA98PevNf1NtC github.com/go-macaron/gzip v0.0.0-20160222043647-cad1c6580a07/go.mod h1://cJFfDp/70L0oTNAMB+M8Jd0rpuIx/55iARuJ6StwE= github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191 h1:NjHlg70DuOkcAMqgt0+XA+NHwtu66MkTVVgR4fFWbcI= github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191/go.mod h1:VFI2o2q9kYsC4o7VP1HrEVosiZZTd+MVT3YZx4gqvJw= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -514,6 +544,7 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8l github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= +github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= @@ -650,6 +681,7 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= @@ -675,6 +707,7 @@ github.com/igm/sockjs-go/v3 v3.0.0 h1:4wLoB9WCnQ8RI87cmqUH778ACDFVmRpkKRCWBeuc+W github.com/igm/sockjs-go/v3 v3.0.0/go.mod h1:UqchsOjeagIBFHvd+RZpLaVRbCwGilEC08EDHsD1jYE= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec h1:CGkYB1Q7DSsH/ku+to+foV4agt2F2miquaLUgF6L178= github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -694,6 +727,10 @@ github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19y github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= +github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= +github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -734,6 +771,9 @@ github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -746,13 +786,20 @@ github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk= +github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U= +github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw= +github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0= @@ -792,6 +839,7 @@ github.com/lufia/iostat v1.1.0/go.mod h1:rEPNA0xXgjHQjuI5Cy05sLlS2oRcSlWHRLrvh/A github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE= github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -815,6 +863,7 @@ github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HN github.com/mattn/go-ieproxy v0.0.0-20191113090002-7c0f6868bffe/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= @@ -832,6 +881,7 @@ github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/mattn/go-xmlrpc v0.0.3/go.mod h1:mqc2dz7tP5x5BKlCahN/n+hs7OSZKJkS9JsHNBRlrxA= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.0/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -841,7 +891,10 @@ github.com/mdlayher/netlink v0.0.0-20190828143259-340058475d09/go.mod h1:KxeJAFO github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= github.com/mdlayher/wifi v0.0.0-20190303161829-b1436901ddee/go.mod h1:Evt/EIne46u9PtQbeTx2NTcqURpr5K4SvKtGmBuDPN8= +github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg= +github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= @@ -877,6 +930,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mozillazg/go-cos v0.13.0/go.mod h1:Zp6DvvXn0RUOXGJ2chmWt2bLEqRAnJnS3DnAZsJsoaE= github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= @@ -888,7 +942,9 @@ github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86w github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= @@ -945,6 +1001,7 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -954,6 +1011,8 @@ github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.5.3-0.20200429092203-e876bbd321b3+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -1041,6 +1100,8 @@ github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7/go.mod h1:Oz4y6ImuOQZxynhbSXk7btjEfNBtGlj2dcaOvXl2FSM= github.com/russellhaering/goxmldsig v0.0.0-20200902171629-2e1fbc2c5593 h1:wkyiSzH81tsd3tSoznvnXMIJo0cpHjbFuJhs/E9t/B8= github.com/russellhaering/goxmldsig v0.0.0-20200902171629-2e1fbc2c5593/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= @@ -1092,13 +1153,17 @@ github.com/soundcloud/go-runit v0.0.0-20150630195641-06ad41a06c4a/go.mod h1:LeFC github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -1140,6 +1205,7 @@ github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/ github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= @@ -1149,9 +1215,12 @@ github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/weaveworks/common v0.0.0-20200206153930-760e36ae819a/go.mod h1:6enWAqfQBFrE8X/XdJwZr8IKgh1chStuFR0mjU/UOUw= github.com/weaveworks/common v0.0.0-20200625145055-4b1847531bc9/go.mod h1:c98fKi5B9u8OsKGiWHLRKus6ToQ1Tubeow44ECO1uxY= @@ -1161,11 +1230,16 @@ github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVT github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xlab/treeprint v1.0.0/go.mod h1:IoImgRak9i3zJyuxOKUP1v4UZd1tMoKkq/Cimt1uhCg= github.com/xorcare/pointer v1.1.0 h1:sFwXOhRF8QZ0tyVZrtxWGIoVZNEmRzBCaFWdONPQIUM= github.com/xorcare/pointer v1.1.0/go.mod h1:6KLhkOh6YbuvZkT4YbxIbR/wzLBjyMxOiNzZhJTor2Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= @@ -1226,6 +1300,7 @@ go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -1304,6 +1379,7 @@ golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1372,6 +1448,7 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1392,6 +1469,7 @@ golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1461,6 +1539,7 @@ golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190118193359-16909d206f00/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1469,6 +1548,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1684,6 +1764,8 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1697,6 +1779,7 @@ gopkg.in/macaron.v1 v1.3.9 h1:Dw+DDRYdXgQyEsPlfAfKz+UA5qVUrH3KPD7JhmZ9MFc= gopkg.in/macaron.v1 v1.3.9/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4= gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/redis.v5 v5.2.9 h1:MNZYOLPomQzZMfpN3ZtD1uyJ2IDonTTlxYiV/pEApiw= gopkg.in/redis.v5 v5.2.9/go.mod h1:6gtv0/+A4iM08kdRfocWYB3bLX2tebpNtfKlFT6H4mY= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/package.json b/package.json index 7f54d67d755..ef39d6af636 100644 --- a/package.json +++ b/package.json @@ -205,6 +205,9 @@ "dependencies": { "@grafana/slate-react": "0.22.9-grafana", "@reduxjs/toolkit": "1.3.4", + "@sentry/browser": "5.25.0", + "@sentry/types": "5.24.2", + "@sentry/utils": "5.24.2", "@torkelo/react-select": "3.0.8", "@types/antlr4": "^4.7.1", "@types/braintree__sanitize-url": "4.0.0", diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index d2cf27cc653..9223477f83d 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -59,6 +59,18 @@ export interface LicenseInfo { edition: string; } +/** + * Describes Sentry integration config + * + * @public + */ +export interface SentryConfig { + enabled: boolean; + dsn: string; + customEndpoint: string; + sampleRate: number; +} + /** * Describes all the different Grafana configuration values available for an instance. * @@ -105,4 +117,5 @@ export interface GrafanaConfig { licenseInfo: LicenseInfo; http2Enabled: boolean; dateFormats?: SystemDateFormatSettings; + sentry: SentryConfig; } diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index fa598ec6cd6..bcb53c23dc8 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -62,6 +62,12 @@ export class GrafanaBootConfig implements GrafanaConfig { rendererAvailable = false; http2Enabled = false; dateFormats?: SystemDateFormatSettings; + sentry = { + enabled: false, + dsn: '', + customEndpoint: '', + sampleRate: 1, + }; marketplaceUrl?: string; constructor(options: GrafanaBootConfig) { diff --git a/packages/grafana-runtime/src/services/EchoSrv.ts b/packages/grafana-runtime/src/services/EchoSrv.ts index d9f1f8b7fd7..098051d5c62 100644 --- a/packages/grafana-runtime/src/services/EchoSrv.ts +++ b/packages/grafana-runtime/src/services/EchoSrv.ts @@ -78,6 +78,7 @@ export interface EchoEvent { export enum EchoEventType { Performance = 'performance', MetaAnalytics = 'meta-analytics', + Sentry = 'sentry', } /** diff --git a/pkg/api/api.go b/pkg/api/api.go index 51ea0904864..ceaf5a50b86 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -445,4 +445,7 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/api/snapshots/:key", Wrap(GetDashboardSnapshot)) r.Get("/api/snapshots-delete/:deleteKey", reqSnapshotPublicModeOrSignedIn, Wrap(DeleteDashboardSnapshotByDeleteKey)) r.Delete("/api/snapshots/:key", reqEditorRole, Wrap(DeleteDashboardSnapshot)) + + // Frontend logs + r.Post("/log", bind(frontendSentryEvent{}), Wrap(hs.logFrontendMessage)) } diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index f8711e584ed..cb82008892c 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -1,5 +1,7 @@ package dtos +import "github.com/grafana/grafana/pkg/setting" + type IndexViewData struct { User *CurrentUser Settings map[string]interface{} @@ -18,6 +20,7 @@ type IndexViewData struct { FavIcon string AppleTouchIcon string AppTitle string + Sentry *setting.Sentry } type PluginCss struct { diff --git a/pkg/api/frontend_logging.go b/pkg/api/frontend_logging.go new file mode 100644 index 00000000000..845535ca09c --- /dev/null +++ b/pkg/api/frontend_logging.go @@ -0,0 +1,90 @@ +package api + +import ( + "fmt" + "strings" + + "github.com/getsentry/sentry-go" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/models" + "github.com/inconshreveable/log15" +) + +var frontendLogger = log.New("frontend") + +type frontendSentryExceptionValue struct { + Value string `json:"value,omitempty"` + Type string `json:"type,omitempty"` + Stacktrace sentry.Stacktrace `json:"stacktrace,omitempty"` +} + +type frontendSentryException struct { + Values []frontendSentryExceptionValue `json:"values,omitempty"` +} + +type frontendSentryEvent struct { + *sentry.Event + Exception *frontendSentryException `json:"exception,omitempty"` +} + +func (value *frontendSentryExceptionValue) FmtMessage() string { + return fmt.Sprintf("%s: %s", value.Type, value.Value) +} + +func (value *frontendSentryExceptionValue) FmtStacktrace() string { + var stacktrace = value.FmtMessage() + for _, frame := range value.Stacktrace.Frames { + stacktrace += fmt.Sprintf("\n at %s (%s:%v:%v)", frame.Function, frame.Filename, frame.Lineno, frame.Colno) + } + return stacktrace +} + +func (exception *frontendSentryException) FmtStacktraces() string { + var stacktraces []string + for _, value := range exception.Values { + stacktraces = append(stacktraces, value.FmtStacktrace()) + } + return strings.Join(stacktraces, "\n\n") +} + +func (event *frontendSentryEvent) ToLogContext() log15.Ctx { + var ctx = make(log15.Ctx) + ctx["url"] = event.Request.URL + ctx["user_agent"] = event.Request.Headers["User-Agent"] + ctx["event_id"] = event.EventID + ctx["original_timestamp"] = event.Timestamp + if event.Exception != nil { + ctx["stacktrace"] = event.Exception.FmtStacktraces() + } + if len(event.User.Email) > 0 { + ctx["user_email"] = event.User.Email + ctx["user_id"] = event.User.ID + } + + return ctx +} + +func (hs *HTTPServer) logFrontendMessage(c *models.ReqContext, event frontendSentryEvent) Response { + var msg = "unknown" + + if len(event.Message) > 0 { + msg = event.Message + } else if event.Exception != nil && len(event.Exception.Values) > 0 { + msg = event.Exception.Values[0].FmtMessage() + } + + var ctx = event.ToLogContext() + + switch event.Level { + case sentry.LevelError: + frontendLogger.Error(msg, ctx) + case sentry.LevelWarning: + frontendLogger.Warn(msg, ctx) + case sentry.LevelDebug: + frontendLogger.Debug(msg, ctx) + default: + frontendLogger.Info(msg, ctx) + } + + return Success("ok") +} diff --git a/pkg/api/frontend_logging_test.go b/pkg/api/frontend_logging_test.go new file mode 100644 index 00000000000..381436fe0b5 --- /dev/null +++ b/pkg/api/frontend_logging_test.go @@ -0,0 +1,149 @@ +package api + +import ( + "net/http" + "testing" + "time" + + "github.com/getsentry/sentry-go" + "github.com/grafana/grafana/pkg/models" + log "github.com/inconshreveable/log15" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type logScenarioFunc func(c *scenarioContext, logs []*log.Record) + +func logSentryEventScenario(t *testing.T, desc string, event frontendSentryEvent, fn logScenarioFunc) { + t.Run(desc, func(t *testing.T) { + logs := []*log.Record{} + origHandler := frontendLogger.GetHandler() + frontendLogger.SetHandler(log.FuncHandler(func(r *log.Record) error { + logs = append(logs, r) + return nil + })) + t.Cleanup(func() { + frontendLogger.SetHandler(origHandler) + }) + + sc := setupScenarioContext("/log") + hs := HTTPServer{} + + handler := Wrap(func(w http.ResponseWriter, c *models.ReqContext) Response { + sc.context = c + return hs.logFrontendMessage(c, event) + }) + + sc.m.Post(sc.url, handler) + sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() + fn(sc, logs) + }) +} + +func TestFrontendLoggingEndpoint(t *testing.T) { + ts, err := time.Parse("2006-01-02T15:04:05.000Z", "2020-10-22T06:29:29.078Z") + require.NoError(t, err) + + t.Run("FrontendLoggingEndpoint", func(t *testing.T) { + request := sentry.Request{ + URL: "http://localhost:3000/", + Headers: map[string]string{ + "User-Agent": "Chrome", + }, + } + + user := sentry.User{ + Email: "geralt@kaermorhen.com", + ID: "45", + } + + errorEvent := frontendSentryEvent{ + &sentry.Event{ + EventID: "123", + Level: sentry.LevelError, + Request: &request, + Timestamp: ts, + }, + &frontendSentryException{ + Values: []frontendSentryExceptionValue{ + { + Type: "UserError", + Value: "Please replace user and try again", + Stacktrace: sentry.Stacktrace{ + Frames: []sentry.Frame{ + { + Function: "foofn", + Filename: "foo.js", + Lineno: 123, + Colno: 23, + }, + { + Function: "barfn", + Filename: "bar.js", + Lineno: 113, + Colno: 231, + }, + }, + }, + }, + }, + }, + } + + logSentryEventScenario(t, "Should log received error event", errorEvent, func(sc *scenarioContext, logs []*log.Record) { + assert.Equal(t, 200, sc.resp.Code) + assert.Len(t, logs, 1) + assertContextContains(t, logs[0], "logger", "frontend") + assertContextContains(t, logs[0], "url", errorEvent.Request.URL) + assertContextContains(t, logs[0], "user_agent", errorEvent.Request.Headers["User-Agent"]) + assertContextContains(t, logs[0], "event_id", errorEvent.EventID) + assertContextContains(t, logs[0], "original_timestamp", errorEvent.Timestamp) + assertContextContains(t, logs[0], "stacktrace", `UserError: Please replace user and try again + at foofn (foo.js:123:23) + at barfn (bar.js:113:231)`) + }) + + messageEvent := frontendSentryEvent{ + &sentry.Event{ + EventID: "123", + Level: sentry.LevelInfo, + Request: &request, + Timestamp: ts, + Message: "hello world", + User: user, + }, + nil, + } + + logSentryEventScenario(t, "Should log received message event", messageEvent, func(sc *scenarioContext, logs []*log.Record) { + assert.Equal(t, 200, sc.resp.Code) + assert.Len(t, logs, 1) + assert.Equal(t, "hello world", logs[0].Msg) + assert.Equal(t, log.LvlInfo, logs[0].Lvl) + assertContextContains(t, logs[0], "logger", "frontend") + assertContextContains(t, logs[0], "url", messageEvent.Request.URL) + assertContextContains(t, logs[0], "user_agent", messageEvent.Request.Headers["User-Agent"]) + assertContextContains(t, logs[0], "event_id", messageEvent.EventID) + assertContextContains(t, logs[0], "original_timestamp", messageEvent.Timestamp) + assert.NotContains(t, logs[0].Ctx, "stacktrace") + assertContextContains(t, logs[0], "user_email", user.Email) + assertContextContains(t, logs[0], "user_id", user.ID) + }) + }) +} + +func indexOf(arr []interface{}, item string) int { + for i, elem := range arr { + if elem == item { + return i + } + } + return -1 +} + +func assertContextContains(t *testing.T, logRecord *log.Record, label string, value interface{}) { + assert.Contains(t, logRecord.Ctx, label) + labelIdx := indexOf(logRecord.Ctx, label) + assert.Equal(t, value, logRecord.Ctx[labelIdx+1]) +} diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 139fccdeca3..83cb44e74c2 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -240,6 +240,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i "featureToggles": hs.Cfg.FeatureToggles, "rendererAvailable": hs.RenderService.IsAvailable(), "http2Enabled": hs.Cfg.Protocol == setting.HTTP2Scheme, + "sentry": hs.Cfg.Sentry, "marketplaceUrl": hs.Cfg.MarketplaceURL, } diff --git a/pkg/api/index.go b/pkg/api/index.go index 8227f190e30..7832bbdd29e 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -403,6 +403,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat AppleTouchIcon: "public/img/apple-touch-icon.png", AppTitle: "Grafana", NavTree: navTree, + Sentry: &hs.Cfg.Sentry, } if setting.DisableGravatar { diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index b418a8f65dd..6b3cb83f1bb 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -322,6 +322,9 @@ type Cfg struct { AlertingAnnotationCleanupSetting AnnotationCleanupSettings DashboardAnnotationCleanupSettings AnnotationCleanupSettings APIAnnotationCleanupSettings AnnotationCleanupSettings + + // Sentry config + Sentry Sentry } // IsExpressionsEnabled returns whether the expressions feature is enabled. @@ -846,6 +849,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { } cfg.readDateFormats() + cfg.readSentryConfig() return nil } diff --git a/pkg/setting/setting_sentry.go b/pkg/setting/setting_sentry.go new file mode 100644 index 00000000000..4c221b6b4b1 --- /dev/null +++ b/pkg/setting/setting_sentry.go @@ -0,0 +1,18 @@ +package setting + +type Sentry struct { + Enabled bool `json:"enabled"` + DSN string `json:"dsn"` + CustomEndpoint string `json:"customEndpoint"` + SampleRate float64 `json:"sampleRate"` +} + +func (cfg *Cfg) readSentryConfig() { + raw := cfg.Raw.Section("log.frontend") + cfg.Sentry = Sentry{ + Enabled: raw.Key("enabled").MustBool(true), + DSN: raw.Key("sentry_dsn").String(), + CustomEndpoint: raw.Key("custom_endpoint").String(), + SampleRate: raw.Key("sample_rate").MustFloat64(), + } +} diff --git a/public/app/app.ts b/public/app/app.ts index 3981ad7ef1c..74da01a5788 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -49,6 +49,7 @@ import { getStandardFieldConfigs, getStandardOptionEditors, getScrollbarWidth } import { getDefaultVariableAdapters, variableAdapters } from './features/variables/adapters'; import { initDevFeatures } from './dev'; import { getStandardTransformers } from 'app/core/utils/standardTransformers'; +import { SentryEchoBackend } from './core/services/echo/backends/sentry/SentryBackend'; import { monkeyPatchInjectorWithPreAssignedBindings } from './core/injectorMonkeyPatch'; // add move to lodash for backward compatabiltiy @@ -205,6 +206,13 @@ export class GrafanaApp { }); registerEchoBackend(new PerformanceBackend({})); + registerEchoBackend( + new SentryEchoBackend({ + ...config.sentry, + user: config.bootData.user, + buildInfo: config.buildInfo, + }) + ); window.addEventListener('DOMContentLoaded', () => { reportPerformance('dcl', Math.round(performance.now())); diff --git a/public/app/core/services/echo/EchoSrv.ts b/public/app/core/services/echo/EchoSrv.ts index e8eb01e9c61..e6d793f7a2c 100644 --- a/public/app/core/services/echo/EchoSrv.ts +++ b/public/app/core/services/echo/EchoSrv.ts @@ -1,4 +1,5 @@ import { getEchoSrv, EchoEventType } from '@grafana/runtime'; +import { captureException } from '@sentry/browser'; import { PerformanceEvent } from './backends/PerformanceBackend'; export const reportPerformance = (metric: string, value: number) => { @@ -10,3 +11,7 @@ export const reportPerformance = (metric: string, value: number) => { }, }); }; + +// Sentry will process the error, adding it's own metadata, applying any sampling rules, +// then push it to EchoSrv as SentryEvent +export const reportError = (error: Error) => captureException(error); diff --git a/public/app/core/services/echo/backends/sentry/SentryBackend.test.ts b/public/app/core/services/echo/backends/sentry/SentryBackend.test.ts new file mode 100644 index 00000000000..d6d78ae4f59 --- /dev/null +++ b/public/app/core/services/echo/backends/sentry/SentryBackend.test.ts @@ -0,0 +1,131 @@ +import { init as initSentry, setUser as sentrySetUser, Event as SentryEvent } from '@sentry/browser'; +import { SentryEchoBackend, SentryEchoBackendOptions } from './SentryBackend'; +import { BuildInfo } from '@grafana/data'; +import { FetchTransport } from '@sentry/browser/dist/transports'; +import { CustomEndpointTransport } from './transports/CustomEndpointTransport'; +import { EchoSrvTransport } from './transports/EchoSrvTransport'; +import { SentryEchoEvent } from './types'; +import { EchoBackend, EchoEventType, EchoMeta, setEchoSrv } from '@grafana/runtime'; +import { waitFor } from '@testing-library/react'; +import { Echo } from '../../Echo'; + +jest.mock('@sentry/browser'); + +describe('SentryEchoBackend', () => { + beforeEach(() => jest.resetAllMocks()); + + const buildInfo: BuildInfo = { + version: '1.0', + commit: 'abcd123', + isEnterprise: false, + env: 'production', + edition: "Director's cut", + latestVersion: 'ba', + hasUpdate: false, + hideVersion: false, + }; + + const options: SentryEchoBackendOptions = { + enabled: true, + buildInfo, + dsn: 'https://examplePublicKey@o0.ingest.testsentry.io/0', + sampleRate: 1, + customEndpoint: '', + user: { + email: 'darth.vader@sith.glx', + id: 504, + }, + }; + + it('will set up sentry`s FetchTransport if DSN is provided', async () => { + const backend = new SentryEchoBackend(options); + expect(backend.transports.length).toEqual(1); + expect(backend.transports[0]).toBeInstanceOf(FetchTransport); + expect((backend.transports[0] as FetchTransport).options.dsn).toEqual(options.dsn); + }); + + it('will set up custom endpoint transport if custom endpoint is provided', async () => { + const backend = new SentryEchoBackend({ + ...options, + dsn: '', + customEndpoint: '/log', + }); + expect(backend.transports.length).toEqual(1); + expect(backend.transports[0]).toBeInstanceOf(CustomEndpointTransport); + expect((backend.transports[0] as CustomEndpointTransport).options.endpoint).toEqual('/log'); + }); + + it('will initialize sentry and set user', async () => { + new SentryEchoBackend(options); + expect(initSentry).toHaveBeenCalledTimes(1); + expect(initSentry).toHaveBeenCalledWith({ + release: buildInfo.version, + environment: buildInfo.env, + dsn: options.dsn, + sampleRate: options.sampleRate, + transport: EchoSrvTransport, + }); + expect(sentrySetUser).toHaveBeenCalledWith({ + email: options.user?.email, + id: String(options.user?.id), + }); + }); + + it('will forward events to transports', async () => { + const backend = new SentryEchoBackend(options); + backend.transports = [{ sendEvent: jest.fn() }, { sendEvent: jest.fn() }]; + const event: SentryEchoEvent = { + type: EchoEventType.Sentry, + payload: ({ foo: 'bar' } as unknown) as SentryEvent, + meta: ({} as unknown) as EchoMeta, + }; + backend.addEvent(event); + backend.transports.forEach(transport => { + expect(transport.sendEvent).toHaveBeenCalledTimes(1); + expect(transport.sendEvent).toHaveBeenCalledWith(event.payload); + }); + }); + + it('integration test with EchoSrv, Sentry and CustomFetchTransport', async () => { + // sets up the whole thing between window.onerror and backend endpoint call, checks that error is reported + + // use actual sentry & mock window.fetch + const sentry = jest.requireActual('@sentry/browser'); + (initSentry as jest.Mock).mockImplementation(sentry.init); + (sentrySetUser as jest.Mock).mockImplementation(sentry.setUser); + const fetchSpy = (window.fetch = jest.fn()); + fetchSpy.mockResolvedValue({ status: 200 } as Response); + + // set up echo srv & sentry backend + const echo = new Echo({ debug: true }); + setEchoSrv(echo); + const sentryBackend = new SentryEchoBackend({ + ...options, + dsn: '', + customEndpoint: '/log', + }); + echo.addBackend(sentryBackend); + + // lets add another echo backend for sentry events for good measure + const myCustomErrorBackend: EchoBackend = { + supportedEvents: [EchoEventType.Sentry], + flush: () => {}, + options: {}, + addEvent: jest.fn(), + }; + echo.addBackend(myCustomErrorBackend); + + // fire off an error using global error handler, Sentry should pick it up + const error = new Error('test error'); + window.onerror!(error.message, undefined, undefined, undefined, error); + + // check that error was reported to backend + await waitFor(() => expect(fetchSpy).toHaveBeenCalledTimes(1)); + const [url, reqInit]: [string, RequestInit] = fetchSpy.mock.calls[0]; + expect(url).toEqual('/log'); + expect((JSON.parse(reqInit.body as string) as SentryEvent).exception!.values![0].value).toEqual('test error'); + + // check that our custom backend got it too + expect(myCustomErrorBackend.addEvent).toHaveBeenCalledTimes(1); + }); +}); diff --git a/public/app/core/services/echo/backends/sentry/SentryBackend.ts b/public/app/core/services/echo/backends/sentry/SentryBackend.ts new file mode 100644 index 00000000000..d2a462c1957 --- /dev/null +++ b/public/app/core/services/echo/backends/sentry/SentryBackend.ts @@ -0,0 +1,59 @@ +import { EchoBackend, EchoEventType } from '@grafana/runtime'; +import { SentryConfig } from '@grafana/data/src/types/config'; +import { BrowserOptions, init as initSentry, setUser as sentrySetUser } from '@sentry/browser'; +import { FetchTransport } from '@sentry/browser/dist/transports'; +import { CustomEndpointTransport } from './transports/CustomEndpointTransport'; +import { EchoSrvTransport } from './transports/EchoSrvTransport'; +import { BuildInfo } from '@grafana/data'; +import { SentryEchoEvent, User, BaseTransport } from './types'; + +export interface SentryEchoBackendOptions extends SentryConfig { + user?: User; + buildInfo: BuildInfo; +} + +export class SentryEchoBackend implements EchoBackend { + supportedEvents = [EchoEventType.Sentry]; + + transports: BaseTransport[]; + + constructor(public options: SentryEchoBackendOptions) { + // set up transports to post events to grafana backend and/or Sentry + this.transports = []; + if (options.dsn) { + this.transports.push(new FetchTransport({ dsn: options.dsn })); + } + if (options.customEndpoint) { + this.transports.push(new CustomEndpointTransport({ endpoint: options.customEndpoint })); + } + + // initialize Sentry so it can set up it's hooks and start collecting errors + const sentryOptions: BrowserOptions = { + release: options.buildInfo.version, + environment: options.buildInfo.env, + // seems Sentry won't attempt to send events to transport unless a valid DSN is defined :shrug: + dsn: options.dsn || 'https://examplePublicKey@o0.ingest.sentry.io/0', + sampleRate: options.sampleRate, + transport: EchoSrvTransport, // will dump errors to EchoSrv + }; + + if (options.user) { + sentrySetUser({ + email: options.user.email, + id: String(options.user.id), + }); + } + + initSentry(sentryOptions); + } + + addEvent = (e: SentryEchoEvent) => { + this.transports.forEach(t => t.sendEvent(e.payload)); + }; + + // backend will log events to stdout, and at least in case of hosted grafana they will be + // ingested into Loki. Due to Loki limitations logs cannot be backdated, + // so not using buffering for this backend to make sure that events are logged as close + // to their context as possible + flush = () => {}; +} diff --git a/public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.test.ts b/public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.test.ts new file mode 100644 index 00000000000..299bdb62f87 --- /dev/null +++ b/public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.test.ts @@ -0,0 +1,64 @@ +import { Event, Severity } from '@sentry/browser'; +import { CustomEndpointTransport } from './CustomEndpointTransport'; + +describe('CustomEndpointTransport', () => { + const fetchSpy = (window.fetch = jest.fn()); + beforeEach(() => jest.resetAllMocks()); + const now = new Date(); + + const event: Event = { + level: Severity.Error, + breadcrumbs: [], + exception: { + values: [ + { + type: 'SomeError', + value: 'foo', + }, + ], + }, + timestamp: now.getTime() / 1000, + }; + + it('will send received event to backend using window.fetch', async () => { + fetchSpy.mockResolvedValue({ status: 200 } as Response); + const transport = new CustomEndpointTransport({ endpoint: '/log' }); + await transport.sendEvent(event); + expect(fetchSpy).toHaveBeenCalledTimes(1); + const [url, reqInit]: [string, RequestInit] = fetchSpy.mock.calls[0]; + expect(url).toEqual('/log'); + expect(reqInit.method).toEqual('POST'); + expect(reqInit.headers).toEqual({ + 'Content-Type': 'application/json', + }); + expect(JSON.parse(reqInit.body as string)).toEqual({ + ...event, + timestamp: now.toISOString(), + }); + }); + + it('will back off if backend returns Retry-After', async () => { + const rateLimiterResponse = { + status: 429, + ok: false, + headers: (new Headers({ + 'Retry-After': '1', // 1 second + }) as any) as Headers, + } as Response; + fetchSpy.mockResolvedValueOnce(rateLimiterResponse).mockResolvedValueOnce({ status: 200 } as Response); + const transport = new CustomEndpointTransport({ endpoint: '/log' }); + + // first call - backend is called, rejected because of 429 + await expect(transport.sendEvent(event)).rejects.toEqual(rateLimiterResponse); + expect(fetchSpy).toHaveBeenCalledTimes(1); + + // second immediate call - shot circuited because retry-after time has not expired, backend not called + await expect(transport.sendEvent(event)).rejects.toBeTruthy(); + expect(fetchSpy).toHaveBeenCalledTimes(1); + + // wait out the retry-after and call again - great success + await new Promise(resolve => setTimeout(() => resolve(), 1001)); + await expect(transport.sendEvent(event)).resolves.toBeTruthy(); + expect(fetchSpy).toHaveBeenCalledTimes(2); + }); +}); diff --git a/public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.ts b/public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.ts new file mode 100644 index 00000000000..f856b3d1ba9 --- /dev/null +++ b/public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.ts @@ -0,0 +1,118 @@ +import { Event, Severity } from '@sentry/browser'; +import { logger, parseRetryAfterHeader, PromiseBuffer, supportsReferrerPolicy, SyncPromise } from '@sentry/utils'; +import { Response, Status } from '@sentry/types'; +import { BaseTransport } from '../types'; + +export interface CustomEndpointTransportOptions { + endpoint: string; + fetchParameters?: Partial; +} + +/** + * This is a copy of sentry's FetchTransport, edited to be able to push to any custom url + * instead of using Sentry-specific endpoint logic. + * Also transofrms some of the payload values to be parseable by go. + * Sends events sequanetially and implements back-off in case of rate limiting. + */ + +export class CustomEndpointTransport implements BaseTransport { + /** Locks transport after receiving 429 response */ + private _disabledUntil: Date = new Date(Date.now()); + + private readonly _buffer: PromiseBuffer = new PromiseBuffer(30); + + constructor(public options: CustomEndpointTransportOptions) {} + + sendEvent(event: Event): PromiseLike { + if (new Date(Date.now()) < this._disabledUntil) { + return Promise.reject({ + event, + reason: `Transport locked till ${this._disabledUntil} due to too many requests.`, + status: 429, + }); + } + + const sentryReq = { + // convert all timestamps to iso string, so it's parseable by backend + body: JSON.stringify({ + ...event, + level: event.level ?? (event.exception ? Severity.Error : Severity.Info), + exception: event.exception + ? { + values: event.exception.values?.map(value => ({ + ...value, + // according to both typescript and go types, value is supposed to be string. + // but in some odd cases at runtime it turns out to be an empty object {} + // let's fix it here + value: fmtSentryErrorValue(value.value), + })), + } + : event.exception, + breadcrumbs: event.breadcrumbs?.map(breadcrumb => ({ + ...breadcrumb, + timestamp: makeTimestamp(breadcrumb.timestamp), + })), + timestamp: makeTimestamp(event.timestamp), + }), + url: this.options.endpoint, + }; + + const options: RequestInit = { + body: sentryReq.body, + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + // Despite all stars in the sky saying that Edge supports old draft syntax, aka 'never', 'always', 'origin' and 'default + // https://caniuse.com/#feat=referrer-policy + // It doesn't. And it throw exception instead of ignoring this parameter... + // REF: https://github.com/getsentry/raven-js/issues/1233 + referrerPolicy: (supportsReferrerPolicy() ? 'origin' : '') as ReferrerPolicy, + }; + + if (this.options.fetchParameters !== undefined) { + Object.assign(options, this.options.fetchParameters); + } + + return this._buffer.add( + new SyncPromise((resolve, reject) => { + window + .fetch(sentryReq.url, options) + .then(response => { + const status = Status.fromHttpCode(response.status); + + if (status === Status.Success) { + resolve({ status }); + return; + } + + if (status === Status.RateLimit) { + const now = Date.now(); + const retryAfterHeader = response.headers.get('Retry-After'); + this._disabledUntil = new Date(now + parseRetryAfterHeader(now, retryAfterHeader)); + logger.warn(`Too many requests, backing off till: ${this._disabledUntil}`); + } + + reject(response); + }) + .catch(reject); + }) + ); + } +} + +function makeTimestamp(time: number | undefined): string { + if (time) { + return new Date(time * 1000).toISOString(); + } + return new Date().toISOString(); +} + +function fmtSentryErrorValue(value: unknown): string | undefined { + if (typeof value === 'string' || value === undefined) { + return value; + } else if (value && typeof value === 'object' && Object.keys(value).length === 0) { + return ''; + } + return String(value); +} diff --git a/public/app/core/services/echo/backends/sentry/transports/EchoSrvTransport.ts b/public/app/core/services/echo/backends/sentry/transports/EchoSrvTransport.ts new file mode 100644 index 00000000000..0e1759ed877 --- /dev/null +++ b/public/app/core/services/echo/backends/sentry/transports/EchoSrvTransport.ts @@ -0,0 +1,14 @@ +import { getEchoSrv, EchoEventType } from '@grafana/runtime'; +import { BaseTransport } from '@sentry/browser/dist/transports'; +import { Event } from '@sentry/browser'; +import { Status } from '@sentry/types'; + +export class EchoSrvTransport extends BaseTransport { + sendEvent(event: Event) { + getEchoSrv().addEvent({ + type: EchoEventType.Sentry, + payload: event, + }); + return Promise.resolve({ status: Status.Success, event }); + } +} diff --git a/public/app/core/services/echo/backends/sentry/types.ts b/public/app/core/services/echo/backends/sentry/types.ts new file mode 100644 index 00000000000..4f036183ec9 --- /dev/null +++ b/public/app/core/services/echo/backends/sentry/types.ts @@ -0,0 +1,14 @@ +import { EchoEvent, EchoEventType } from '@grafana/runtime'; +import { Event as SentryEvent } from '@sentry/browser'; +import { Response } from '@sentry/types'; + +export interface BaseTransport { + sendEvent(event: SentryEvent): PromiseLike; +} + +export type SentryEchoEvent = EchoEvent; + +export interface User { + email: string; + id: number; +} diff --git a/yarn.lock b/yarn.lock index db7dce47fc2..4347e24c176 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5166,6 +5166,71 @@ dependencies: any-observable "^0.3.0" +"@sentry/browser@5.25.0": + version "5.25.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.25.0.tgz#4e3d2132ba1f2e2b26f73c49cbb6977ee9c9fea9" + integrity sha512-QDVUbUuTu58xCdId0eUO4YzpvrPdoUw1ryVy/Yep9Es/HD0fiSyO1Js0eQVkV/EdXtyo2pomc1Bpy7dbn2EJ2w== + dependencies: + "@sentry/core" "5.25.0" + "@sentry/types" "5.25.0" + "@sentry/utils" "5.25.0" + tslib "^1.9.3" + +"@sentry/core@5.25.0": + version "5.25.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.25.0.tgz#525ad37f9e8a95603768e3b74b437d5235a51578" + integrity sha512-hY6Zmo7t/RV+oZuvXHP6nyAj/QnZr2jW0e7EbL5YKMV8q0vlnjcE0LgqFXme726OJemoLk67z+sQOJic/Ztehg== + dependencies: + "@sentry/hub" "5.25.0" + "@sentry/minimal" "5.25.0" + "@sentry/types" "5.25.0" + "@sentry/utils" "5.25.0" + tslib "^1.9.3" + +"@sentry/hub@5.25.0": + version "5.25.0" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.25.0.tgz#6932535604cafaee1ac7f361b0e7c2ce8f7e7bc3" + integrity sha512-kOlOiJV8wMX50lYpzMlOXBoH7MNG0Ho4RTusdZnXZBaASq5/ljngDJkLr6uylNjceZQP21wzipCQajsJMYB7EQ== + dependencies: + "@sentry/types" "5.25.0" + "@sentry/utils" "5.25.0" + tslib "^1.9.3" + +"@sentry/minimal@5.25.0": + version "5.25.0" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.25.0.tgz#447b5406b45c8c436c461abea4474d6a849ed975" + integrity sha512-9JFKuW7U+1vPO86k3+XRtJyooiVZsVOsFFO4GulBzepi3a0ckNyPgyjUY1saLH+cEHx18hu8fGgajvI8ANUF2g== + dependencies: + "@sentry/hub" "5.25.0" + "@sentry/types" "5.25.0" + tslib "^1.9.3" + +"@sentry/types@5.24.2": + version "5.24.2" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.24.2.tgz#e2c25d1e75d8dbec5dbbd9a309a321425b61c2ca" + integrity sha512-HcOK00R0tQG5vzrIrqQ0jC28+z76jWSgQCzXiessJ5SH/9uc6NzdO7sR7K8vqMP2+nweCHckFohC8G0T1DLzuQ== + +"@sentry/types@5.25.0": + version "5.25.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.25.0.tgz#3bcf95e118d655d3f4e8bfa5f0be2e1fe4ea5307" + integrity sha512-8M4PREbcar+15wrtEqcwfcU33SS+2wBSIOd/NrJPXJPTYxi49VypCN1mZBDyWkaK+I+AuQwI3XlRPCfsId3D1A== + +"@sentry/utils@5.24.2": + version "5.24.2" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.24.2.tgz#90b7dff939bbbf4bb8edcac6aac2d04a0552af80" + integrity sha512-oPGde4tNEDHKk0Cg9q2p0qX649jLDUOwzJXHKpd0X65w3A6eJByDevMr8CSzKV9sesjrUpxqAv6f9WWlz185tA== + dependencies: + "@sentry/types" "5.24.2" + tslib "^1.9.3" + +"@sentry/utils@5.25.0": + version "5.25.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.25.0.tgz#b132034be66d7381d30879d2a9e09216fed28342" + integrity sha512-Hz5spdIkMSRH5NR1YFOp5qbsY5Ud2lKhEQWlqxcVThMG5YNUc10aYv5ijL19v0YkrC2rqPjCRm7GrVtzOc7bXQ== + dependencies: + "@sentry/types" "5.25.0" + tslib "^1.9.3" + "@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0": version "1.7.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.0.tgz#f90ffc52a2e519f018b13b6c4da03cbff36ebed6"