Move notification historian to grafana/alerting
This commit is contained in:
@@ -90,7 +90,7 @@ require (
|
||||
github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 // @grafana/identity-access-team
|
||||
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
|
||||
github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics
|
||||
github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 // @grafana/grafana-backend-group
|
||||
github.com/grafana/dskit v0.0.0-20250723143816-ff33c5829b96 // @grafana/grafana-backend-group
|
||||
github.com/grafana/e2e v0.1.1 // @grafana-app-platform-squad
|
||||
github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f // @grafana/sharing-squad
|
||||
github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d // @grafana/grafana-operator-experience-squad
|
||||
@@ -627,3 +627,5 @@ exclude k8s.io/client-go v12.0.0+incompatible
|
||||
// only used to run tests and not required for building the Grafana binary.
|
||||
// Since the test data doesn't contain a license file we exclude it.
|
||||
exclude github.com/RoaringBitmap/real-roaring-datasets v0.0.0-20190726190000-eb7c87156f76
|
||||
|
||||
replace github.com/grafana/alerting => ../alerting
|
||||
|
||||
@@ -1445,9 +1445,8 @@ github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3 h1:6amM4HsNPOvMLVc2ZnyqrjeQ92YAVWn7T4WBKK87inY=
|
||||
github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
|
||||
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
@@ -1580,8 +1579,6 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grafana/alerting v0.0.0-20250729175202-b4b881b7b263 h1:hcr/AmPB0KL4H+gCEFIdKUnkihTxGAkAOiZA7GDYoL8=
|
||||
github.com/grafana/alerting v0.0.0-20250729175202-b4b881b7b263/go.mod h1:VKxaR93Gff0ZlO2sPcdPVob1a/UzArFEW5zx3Bpyhls=
|
||||
github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 h1:vVPT0i5Y1vI6qzecYStV2yk7cHKrC3Pc7AgvwT5KydQ=
|
||||
github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43/go.mod h1:1fWkOiL+m32NBgRHZtlZGz2ji868tPZACYbqP3nBRJI=
|
||||
github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 h1:NlkGMnVi/oUn6Cr90QbJYpQJ4FnjyAIG9Ex5GtTZIzw=
|
||||
@@ -1590,8 +1587,8 @@ github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmO
|
||||
github.com/grafana/dataplane/examples v0.0.1/go.mod h1:h5YwY8s407/17XF5/dS8XrUtsTVV2RnuW8+m1Mp46mg=
|
||||
github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6kE/MWfg7s=
|
||||
github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU=
|
||||
github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 h1:qcSGhr691f1mmPHwg2svGyO40Ex92G02aOyHzP6XHCE=
|
||||
github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914/go.mod h1:OiN4P4aC6LwLzLbEupH3Ue83VfQoNMfG48rsna8jI/E=
|
||||
github.com/grafana/dskit v0.0.0-20250723143816-ff33c5829b96 h1:Wsc21GtcFgqqVuP6SCKaQDoliVW5Agcrmb3PYBUwYS4=
|
||||
github.com/grafana/dskit v0.0.0-20250723143816-ff33c5829b96/go.mod h1:kImsvJ1xnmeT9Z6StK+RdEKLzlpzBsKwJbEQfmBJdFs=
|
||||
github.com/grafana/e2e v0.1.1 h1:/b6xcv5BtoBnx8cZnCiey9DbjEc8z7gXHO5edoeRYxc=
|
||||
github.com/grafana/e2e v0.1.1/go.mod h1:RpNLgae5VT+BUHvPE+/zSypmOXKwEu4t+tnEMS1ATaE=
|
||||
github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f h1:5xkjl5Y/j2QefJKOtTfyD1wXlVsQ2yEXmd0u82h5obs=
|
||||
|
||||
+46
@@ -553,6 +553,8 @@ github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0 h1:kAtNAWwvTt5+iew6baV0kbO
|
||||
github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0/go.mod h1:VRKXU8C7Y/aUKjRBTGfw0Ndv4YqNxlB8zAPJJDxbASE=
|
||||
github.com/GoogleCloudPlatform/cloudsql-proxy v1.37.6 h1:UucmvNRPE75F3KzT68GHhKzOPwttxiFkh1d5LTTywW8=
|
||||
github.com/GoogleCloudPlatform/cloudsql-proxy v1.37.6/go.mod h1:XGripOBEUAcge8IUWR/NMAB5qO9k82tkbpoewBpyjYQ=
|
||||
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 h1:DBjmt6/otSdULyJdVg2BlG0qGZO5tKL4VzOs0jpvw5Q=
|
||||
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0/go.mod h1:ZV4VOm0/eHR06JLrXWe09068dHpr3TRpY9Uo7T+anuA=
|
||||
@@ -774,10 +776,12 @@ github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt
|
||||
github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
|
||||
github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892 h1:qg9VbHo1TlL0KDM0vYvBG9EY0X0Yku5WYIPoFWt8f6o=
|
||||
github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE=
|
||||
github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
|
||||
github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
|
||||
github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g=
|
||||
github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA=
|
||||
github.com/dgryski/go-ddmin v0.0.0-20210904190556-96a6d69f1034 h1:BuCyszxPxUjBrYW2HNVrimC0rBUs2U27jCJGVh0IKTM=
|
||||
github.com/dgryski/go-ddmin v0.0.0-20210904190556-96a6d69f1034/go.mod h1:zz4KxBkcXUWKjIcrc+uphJ1gPh/t18ymGm3PmQ+VGTk=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b h1:Yqiad0+sloMPdd/0Fg22actpFx0dekpzt1xJmVNVkU0=
|
||||
github.com/dhui/dktest v0.3.0 h1:kwX5a7EkLcjo7VpsPQSYJcKGbXBXdjI9FGjuUj1jn6I=
|
||||
@@ -844,6 +848,7 @@ github.com/fluent/fluent-bit-go v0.0.0-20230731091245-a7a013e2473c h1:yKN46XJHYC
|
||||
github.com/fluent/fluent-bit-go v0.0.0-20230731091245-a7a013e2473c/go.mod h1:L92h+dgwElEyUuShEwjbiHjseW410WIcNz+Bjutc8YQ=
|
||||
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/fsouza/fake-gcs-server v1.7.0 h1:Un0BXUXrRWYSmYyC1Rqm2e2WJfTPyDy/HGMz31emTi8=
|
||||
github.com/fsouza/fake-gcs-server v1.52.2 h1:j6ne83nqHrlX5EEor7WWVIKdBsztGtwJ1J2mL+k+iio=
|
||||
@@ -904,12 +909,16 @@ github.com/gocraft/dbr/v2 v2.7.2/go.mod h1:5bCqyIXO5fYn3jEp/L06QF4K1siFdhxChMjdN
|
||||
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM=
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12 h1:uK3X/2mt4tbSGoHvbLBHUny7CKiuwUip3MArtukol4E=
|
||||
github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
|
||||
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo=
|
||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||
@@ -1021,20 +1030,31 @@ github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
||||
github.com/hamba/avro/v2 v2.28.0 h1:E8J5D27biyAulWKNiEBhV85QPc9xRMCUCGJewS0KYCE=
|
||||
github.com/hamba/avro/v2 v2.28.0/go.mod h1:9TVrlt1cG1kkTUtm9u2eO5Qb7rZXlYzoKqPt8TSH+TA=
|
||||
github.com/hashicorp/consul/api v1.15.3/go.mod h1:/g/qgcoBcEXALCNZgRRisyTW0nY86++L0KbeAMXYCeY=
|
||||
github.com/hashicorp/consul/sdk v0.11.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw=
|
||||
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8IoK3c=
|
||||
github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg=
|
||||
github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs=
|
||||
github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4=
|
||||
github.com/hashicorp/go-msgpack/v2 v2.1.1/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
||||
github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI=
|
||||
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
|
||||
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
|
||||
github.com/hashicorp/mdns v1.0.5 h1:1M5hW1cunYeoXOqHwEb/GBDDHAFo0Yqb/uz/beC6LbE=
|
||||
github.com/hashicorp/mdns v1.0.5/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||
github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/raft v1.7.0 h1:4u24Qn6lQ6uwziM++UgsyiT64Q8GyRn43CV41qPiz1o=
|
||||
github.com/hashicorp/raft v1.7.0/go.mod h1:N1sKh6Vn47mrWvEArQgILTyng8GoDRNYlgKyK7PMjs0=
|
||||
github.com/hashicorp/raft-wal v0.4.1 h1:aU8XZ6x8R9BAIB/83Z1dTDtXvDVmv9YVYeXxd/1QBSA=
|
||||
github.com/hashicorp/raft-wal v0.4.1/go.mod h1:A6vP5o8hGOs1LHfC1Okh9xPwWDcmb6Vvuz/QyqUXlOE=
|
||||
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
|
||||
github.com/heroku/x v0.0.61 h1:yfoAAtnFWSFZj+UlS+RZL/h8QYEp1R4wHVEg0G+Hwh4=
|
||||
github.com/heroku/x v0.0.61/go.mod h1:C7xYbpMdond+s6L5VpniDUSVPRwm3kZum1o7XiD5ZHk=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
@@ -1170,14 +1190,17 @@ github.com/mfridman/xflag v0.1.0/go.mod h1:/483ywM5ZO5SuMVjrIGquYNE5CzLrj5Ux/LxW
|
||||
github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=
|
||||
github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
|
||||
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc=
|
||||
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mithrandie/readline-csvq v1.3.0 h1:VTJEOGouJ8j27jJCD4kBBbNTxM0OdBvE1aY1tMhlqE8=
|
||||
github.com/mithrandie/readline-csvq v1.3.0/go.mod h1:FKyYqDgf/G4SNov7SMFXRWO6LQLXIOeTog/NB97FZl0=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
@@ -1248,6 +1271,7 @@ github.com/open-telemetry/opentelemetry-collector-contrib/receiver/opencensusrec
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/opencensusreceiver v0.124.1/go.mod h1:4+9pSfniXXdRpkKf0QNdElOd7yIWD4ux8D260tSPV54=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver v0.124.1 h1:XkxqUEoukMWXF+EpEWeM9itXKt62yKi13Lzd8ZEASP4=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver v0.124.1/go.mod h1:CuCZVPz+yn88b5vhZPAlxaMrVuhAVexUV6f8b07lpUc=
|
||||
github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo=
|
||||
github.com/oschwald/geoip2-golang v1.11.0 h1:hNENhCn1Uyzhf9PTmquXENiWS6AlxAEnBII6r8krA3w=
|
||||
github.com/oschwald/geoip2-golang v1.11.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
|
||||
github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
|
||||
@@ -1284,6 +1308,7 @@ github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/j
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/common/assets v0.2.0 h1:0P5OrzoHrYBOSM1OigWL3mY8ZvV2N4zIE/5AahrSrfM=
|
||||
github.com/prometheus/exporter-toolkit v0.10.1-0.20230714054209-2f4150c63f97/go.mod h1:LoBCZeRh+5hX+fSULNyFnagYlQG/gBsyA/deNzROkq8=
|
||||
github.com/prometheus/statsd_exporter v0.26.1 h1:ucbIAdPmwAUcA+dU+Opok8Qt81Aw8HanlO+2N/Wjv7w=
|
||||
github.com/prometheus/statsd_exporter v0.26.1/go.mod h1:XlDdjAmRmx3JVvPPYuFNUg+Ynyb5kR69iPPkQjxXFMk=
|
||||
github.com/pterm/pterm v0.12.80 h1:mM55B+GnKUnLMUSqhdINe4s6tOuVQIetQ3my8JGyAIg=
|
||||
@@ -1308,6 +1333,7 @@ github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529/go.mod h1:qe5TWALJ8/a1
|
||||
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
||||
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245 h1:K1Xf3bKttbF+koVGaX5xngRIZ5bVjbmPnaxE/dR08uY=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk=
|
||||
github.com/sagikazarmark/crypt v0.6.0 h1:REOEXCs/NFY/1jOCEouMuT4zEniE5YoXbvpC5X/TLF8=
|
||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
@@ -1401,6 +1427,8 @@ github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
|
||||
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
|
||||
github.com/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o=
|
||||
github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
|
||||
github.com/uber/jaeger-client-go v2.28.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw=
|
||||
@@ -1455,6 +1483,9 @@ gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/9
|
||||
go.einride.tech/aip v0.68.1 h1:16/AfSxcQISGN5z9C5lM+0mLYXihrHbQ1onvYTr93aQ=
|
||||
go.einride.tech/aip v0.68.1/go.mod h1:XaFtaj4HuA3Zwk9xoBtTWgNubZ0ZZXv9BZJCkuKuWbg=
|
||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
|
||||
go.etcd.io/gofail v0.2.0 h1:p19drv16FKK345a09a1iubchlw/vmRuksmRzgBIGjcA=
|
||||
go.etcd.io/gofail v0.2.0/go.mod h1:nL3ILMGfkXTekKI3clMBNazKnjUZjYLKmBHzsVAnC1o=
|
||||
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||
@@ -1582,6 +1613,7 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.4
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0/go.mod h1:CosX/aS4eHnG9D7nESYpV753l4j9q5j3SL/PUYd2lR8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
|
||||
@@ -1594,6 +1626,7 @@ go.opentelemetry.io/contrib/otelconf v0.15.0 h1:BLNiIUsrNcqhSKpsa6CnhE6LdrpY1A8X
|
||||
go.opentelemetry.io/contrib/otelconf v0.15.0/go.mod h1:OPH1seO5z9dp1P26gnLtoM9ht7JDvh3Ws6XRHuXqImY=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.35.0 h1:DpwKW04LkdFRFCIgM3sqwTJA/QREHMeMHYPWP1WeaPQ=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.35.0/go.mod h1:9+SNxwqvCWo1qQwUpACBY5YKNVxFJn5mlbXg/4+uKBg=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0/go.mod h1:0ciyFyYZxE6JqRAQvIgGRabKWDUmNdW3GAQb6y/RlFU=
|
||||
go.opentelemetry.io/contrib/zpages v0.60.0 h1:wOM9ie1Hz4H88L9KE6GrGbKJhfm+8F1NfW/Y3q9Xt+8=
|
||||
go.opentelemetry.io/contrib/zpages v0.60.0/go.mod h1:xqfToSRGh2MYUsfyErNz8jnNDPlnpZqWM/y6Z2Cx7xw=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
@@ -1612,6 +1645,7 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6o
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.58.0/go.mod h1:7qo/4CLI+zYSNbv0GMNquzuss2FVZo3OYrGh96n4HNc=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY=
|
||||
@@ -1642,6 +1676,7 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8
|
||||
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
|
||||
go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
@@ -1658,10 +1693,12 @@ golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZv
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e h1:qyrTQ++p1afMkO4DPEeLGq/3oTsdlvdH4vqZUBWzUKM=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||
@@ -1675,9 +1712,11 @@ golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20190921015927-1a5e07d1ff72/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
@@ -1702,6 +1741,7 @@ golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
@@ -1759,6 +1799,7 @@ google.golang.org/api v0.229.0/go.mod h1:wyDfmq5g1wYJWn29O22FDWN48P7Xcz0xz+LBppt
|
||||
google.golang.org/api v0.232.0/go.mod h1:p9QCfBWZk1IJETUdbTKloR5ToFdKbYh2fkjsUL6vNoY=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
|
||||
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
|
||||
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc=
|
||||
google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422/go.mod h1:1NPAxoesyw/SgLPqaUp9u1f9PWCLAk/jVmhx7gJZStg=
|
||||
@@ -1781,15 +1822,18 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e/go.
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250425173222-7b384671a197/go.mod h1:Cd8IzgPo5Akum2c9R6FsXNaZbH3Jpa2gpHlW89FqlyQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:W3S/3np0/dPWsWLi1h/UymYctGXaGBM2StwzD0y140U=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
|
||||
google.golang.org/genproto/googleapis/bytestream v0.0.0-20250505200425-f936aa4a68b2 h1:DbpkGFGRkd4GORg+IWQW2EhxUaa/My/PM8d1CGyTDMY=
|
||||
google.golang.org/genproto/googleapis/bytestream v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y=
|
||||
google.golang.org/genproto/googleapis/bytestream v0.0.0-20250512202823-5a2f75b736a9 h1:YI36gCL8AQMhzYN6+jH8PdV/iZ0On+Zd0rO/7lCH3k8=
|
||||
google.golang.org/genproto/googleapis/bytestream v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
|
||||
@@ -1802,7 +1846,9 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||
|
||||
@@ -8,7 +8,8 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/lokiclient"
|
||||
"github.com/grafana/alerting/lokiclient"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/lokiconfig"
|
||||
"golang.org/x/exp/constraints"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
@@ -61,7 +62,7 @@ func NewLokiHistorianStore(cfg setting.UnifiedAlertingStateHistorySettings, db d
|
||||
if !useStore(cfg) {
|
||||
return nil
|
||||
}
|
||||
lokiCfg, err := lokiclient.NewLokiConfig(cfg.LokiSettings)
|
||||
lokiCfg, err := lokiconfig.NewLokiConfig(cfg.LokiSettings)
|
||||
if err != nil {
|
||||
// this config error is already handled elsewhere
|
||||
return nil
|
||||
|
||||
@@ -10,11 +10,12 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/lokiclient"
|
||||
"github.com/grafana/alerting/lokiclient"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/grafana/alerting/client"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
@@ -23,7 +24,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/annotations/testutil"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/client"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/dskit/instrument"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// Requester executes an HTTP request.
|
||||
type Requester interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// TimedClient instruments a request with metrics. It implements Requester.
|
||||
type TimedClient struct {
|
||||
client Requester
|
||||
collector instrument.Collector
|
||||
}
|
||||
|
||||
type contextKey int
|
||||
|
||||
// OperationNameContextKey specifies the operation name location within the context
|
||||
// for instrumentation.
|
||||
const OperationNameContextKey contextKey = 0
|
||||
|
||||
// NewTimedClient creates a Requester that instruments requests on `client`.
|
||||
func NewTimedClient(client Requester, collector instrument.Collector) *TimedClient {
|
||||
return &TimedClient{
|
||||
client: client,
|
||||
collector: collector,
|
||||
}
|
||||
}
|
||||
|
||||
// Do executes the request.
|
||||
func (c TimedClient) Do(r *http.Request) (*http.Response, error) {
|
||||
return TimeRequest(r.Context(), c.operationName(r), c.collector, c.client, r)
|
||||
}
|
||||
|
||||
// RoundTrip implements the RoundTripper interface.
|
||||
func (c TimedClient) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
return c.Do(r)
|
||||
}
|
||||
|
||||
func (c TimedClient) operationName(r *http.Request) string {
|
||||
operation, _ := r.Context().Value(OperationNameContextKey).(string)
|
||||
if operation == "" {
|
||||
operation = r.URL.Path
|
||||
}
|
||||
return operation
|
||||
}
|
||||
|
||||
// TimeRequest performs an HTTP client request and records the duration in a histogram.
|
||||
func TimeRequest(ctx context.Context, operation string, coll instrument.Collector, client Requester, request *http.Request) (*http.Response, error) {
|
||||
var response *http.Response
|
||||
doRequest := func(_ context.Context) error {
|
||||
var err error
|
||||
response, err = client.Do(request) // nolint:bodyclose
|
||||
return err
|
||||
}
|
||||
toStatusCode := func(err error) string {
|
||||
if err == nil {
|
||||
return strconv.Itoa(response.StatusCode)
|
||||
}
|
||||
return "error"
|
||||
}
|
||||
err := instrument.CollectedRequest(ctx, fmt.Sprintf("%s %s", request.Method, operation),
|
||||
coll, toStatusCode, doRequest)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// TracedClient instruments a request with tracing. It implements Requester.
|
||||
type TracedClient struct {
|
||||
client Requester
|
||||
tracer tracing.Tracer
|
||||
name string
|
||||
}
|
||||
|
||||
func NewTracedClient(client Requester, tracer tracing.Tracer, name string) *TracedClient {
|
||||
return &TracedClient{
|
||||
client: client,
|
||||
tracer: tracer,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// Do executes the request.
|
||||
func (c TracedClient) Do(r *http.Request) (*http.Response, error) {
|
||||
ctx, span := c.tracer.Start(r.Context(), c.name, trace.WithSpanKind(trace.SpanKindClient))
|
||||
defer span.End()
|
||||
|
||||
span.SetAttributes(semconv.HTTPURL(r.URL.String()))
|
||||
span.SetAttributes(semconv.HTTPMethod(r.Method))
|
||||
|
||||
c.tracer.Inject(ctx, r.Header, span)
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
resp, err := c.client.Do(r)
|
||||
if err != nil {
|
||||
span.SetStatus(codes.Error, "request failed")
|
||||
span.RecordError(err)
|
||||
} else {
|
||||
if resp.ContentLength > 0 {
|
||||
span.SetAttributes(attribute.Int64("http.content_length", resp.ContentLength))
|
||||
}
|
||||
span.SetAttributes(semconv.HTTPStatusCode(resp.StatusCode))
|
||||
if resp.StatusCode >= 400 && resp.StatusCode < 600 {
|
||||
span.RecordError(fmt.Errorf("error with HTTP status code %d", resp.StatusCode))
|
||||
}
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// RoundTrip implements the RoundTripper interface.
|
||||
func (c TracedClient) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
return c.Do(r)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTimedClient_operationName(t *testing.T) {
|
||||
r, err := http.NewRequest("GET", "https://weave.test", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
r = r.WithContext(context.WithValue(context.Background(), OperationNameContextKey, "opp"))
|
||||
c := NewTimedClient(http.DefaultClient, nil)
|
||||
|
||||
assert.Equal(t, "opp", c.operationName(r))
|
||||
}
|
||||
|
||||
func TestTimedClient_operationName_Default(t *testing.T) {
|
||||
r, err := http.NewRequest("GET", "https://weave.test/you/know/me", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
r = r.WithContext(context.Background())
|
||||
c := NewTimedClient(http.DefaultClient, nil)
|
||||
|
||||
assert.Equal(t, "/you/know/me", c.operationName(r))
|
||||
}
|
||||
@@ -1,333 +0,0 @@
|
||||
package lokiclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/dskit/instrument"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/client"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const defaultPageSize = 1000
|
||||
const maximumPageSize = 5000
|
||||
|
||||
func NewRequester() client.Requester {
|
||||
return &http.Client{}
|
||||
}
|
||||
|
||||
// encoder serializes log streams to some byte format.
|
||||
type encoder interface {
|
||||
// encode serializes a set of log streams to bytes.
|
||||
encode(s []Stream) ([]byte, error)
|
||||
// headers returns a set of HTTP-style headers that describes the encoding scheme used.
|
||||
headers() map[string]string
|
||||
}
|
||||
|
||||
type LokiConfig struct {
|
||||
ReadPathURL *url.URL
|
||||
WritePathURL *url.URL
|
||||
BasicAuthUser string
|
||||
BasicAuthPassword string
|
||||
TenantID string
|
||||
ExternalLabels map[string]string
|
||||
Encoder encoder
|
||||
MaxQueryLength time.Duration
|
||||
MaxQuerySize int
|
||||
}
|
||||
|
||||
func NewLokiConfig(cfg setting.UnifiedAlertingLokiSettings) (LokiConfig, error) {
|
||||
read, write := cfg.LokiReadURL, cfg.LokiWriteURL
|
||||
if read == "" {
|
||||
read = cfg.LokiRemoteURL
|
||||
}
|
||||
if write == "" {
|
||||
write = cfg.LokiRemoteURL
|
||||
}
|
||||
|
||||
if read == "" {
|
||||
return LokiConfig{}, fmt.Errorf("either read path URL or remote Loki URL must be provided")
|
||||
}
|
||||
if write == "" {
|
||||
return LokiConfig{}, fmt.Errorf("either write path URL or remote Loki URL must be provided")
|
||||
}
|
||||
|
||||
readURL, err := url.Parse(read)
|
||||
if err != nil {
|
||||
return LokiConfig{}, fmt.Errorf("failed to parse loki remote read URL: %w", err)
|
||||
}
|
||||
writeURL, err := url.Parse(write)
|
||||
if err != nil {
|
||||
return LokiConfig{}, fmt.Errorf("failed to parse loki remote write URL: %w", err)
|
||||
}
|
||||
|
||||
return LokiConfig{
|
||||
ReadPathURL: readURL,
|
||||
WritePathURL: writeURL,
|
||||
BasicAuthUser: cfg.LokiBasicAuthUsername,
|
||||
BasicAuthPassword: cfg.LokiBasicAuthPassword,
|
||||
TenantID: cfg.LokiTenantID,
|
||||
ExternalLabels: cfg.ExternalLabels,
|
||||
MaxQueryLength: cfg.LokiMaxQueryLength,
|
||||
MaxQuerySize: cfg.LokiMaxQuerySize,
|
||||
// Snappy-compressed protobuf is the default, same goes for Promtail.
|
||||
Encoder: SnappyProtoEncoder{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type HttpLokiClient struct {
|
||||
client client.Requester
|
||||
encoder encoder
|
||||
cfg LokiConfig
|
||||
bytesWritten prometheus.Counter
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// Kind of Operation (=, !=, =~, !~)
|
||||
type Operator string
|
||||
|
||||
const (
|
||||
// Equal operator (=)
|
||||
Eq Operator = "="
|
||||
// Not Equal operator (!=)
|
||||
Neq Operator = "!="
|
||||
// Equal operator supporting RegEx (=~)
|
||||
EqRegEx Operator = "=~"
|
||||
// Not Equal operator supporting RegEx (!~)
|
||||
NeqRegEx Operator = "!~"
|
||||
)
|
||||
|
||||
func NewLokiClient(cfg LokiConfig, req client.Requester, bytesWritten prometheus.Counter, writeDuration *instrument.HistogramCollector, logger log.Logger, tracer tracing.Tracer, spanName string) *HttpLokiClient {
|
||||
tc := client.NewTimedClient(req, writeDuration)
|
||||
trc := client.NewTracedClient(tc, tracer, spanName)
|
||||
return &HttpLokiClient{
|
||||
client: trc,
|
||||
encoder: cfg.Encoder,
|
||||
cfg: cfg,
|
||||
bytesWritten: bytesWritten,
|
||||
log: logger.New("protocol", "http"),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HttpLokiClient) Ping(ctx context.Context) error {
|
||||
log := c.log.FromContext(ctx)
|
||||
uri := c.cfg.ReadPathURL.JoinPath("/loki/api/v1/labels")
|
||||
req, err := http.NewRequest(http.MethodGet, uri.String(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
c.setAuthAndTenantHeaders(req)
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
res, err := c.client.Do(req)
|
||||
if res != nil {
|
||||
defer func() {
|
||||
if err := res.Body.Close(); err != nil {
|
||||
log.Warn("Failed to close response body", "err", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending request: %w", err)
|
||||
}
|
||||
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
return fmt.Errorf("ping request to loki endpoint returned a non-200 status code: %d", res.StatusCode)
|
||||
}
|
||||
log.Debug("Ping request to Loki endpoint succeeded", "status", res.StatusCode)
|
||||
return nil
|
||||
}
|
||||
|
||||
type Stream struct {
|
||||
Stream map[string]string `json:"stream"`
|
||||
Values []Sample `json:"values"`
|
||||
}
|
||||
|
||||
type Sample struct {
|
||||
T time.Time
|
||||
V string
|
||||
}
|
||||
|
||||
func (r *Sample) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal([2]string{
|
||||
fmt.Sprintf("%d", r.T.UnixNano()), r.V,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Sample) UnmarshalJSON(b []byte) error {
|
||||
// A Loki stream sample is formatted like a list with two elements, [At, Val]
|
||||
// At is a string wrapping a timestamp, in nanosecond unix epoch.
|
||||
// Val is a string containing the log line.
|
||||
var tuple [2]string
|
||||
if err := json.Unmarshal(b, &tuple); err != nil {
|
||||
return fmt.Errorf("failed to deserialize sample in Loki response: %w", err)
|
||||
}
|
||||
nano, err := strconv.ParseInt(tuple[0], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("timestamp in Loki sample not convertible to nanosecond epoch: %v", tuple[0])
|
||||
}
|
||||
r.T = time.Unix(0, nano)
|
||||
r.V = tuple[1]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *HttpLokiClient) Push(ctx context.Context, s []Stream) error {
|
||||
log := c.log.FromContext(ctx)
|
||||
enc, err := c.encoder.encode(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uri := c.cfg.WritePathURL.JoinPath("/loki/api/v1/push")
|
||||
req, err := http.NewRequest(http.MethodPost, uri.String(), bytes.NewBuffer(enc))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Loki request: %w", err)
|
||||
}
|
||||
|
||||
c.setAuthAndTenantHeaders(req)
|
||||
for k, v := range c.encoder.headers() {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
|
||||
c.bytesWritten.Add(float64(len(enc)))
|
||||
req = req.WithContext(ctx)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Warn("Failed to close response body", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = c.handleLokiResponse(log, resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *HttpLokiClient) setAuthAndTenantHeaders(req *http.Request) {
|
||||
if c.cfg.BasicAuthUser != "" || c.cfg.BasicAuthPassword != "" {
|
||||
req.SetBasicAuth(c.cfg.BasicAuthUser, c.cfg.BasicAuthPassword)
|
||||
}
|
||||
|
||||
if c.cfg.TenantID != "" {
|
||||
req.Header.Add("X-Scope-OrgID", c.cfg.TenantID)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HttpLokiClient) RangeQuery(ctx context.Context, logQL string, start, end, limit int64) (QueryRes, error) {
|
||||
log := c.log.FromContext(ctx)
|
||||
// Run the pre-flight checks for the query.
|
||||
if start > end {
|
||||
return QueryRes{}, fmt.Errorf("start time cannot be after end time")
|
||||
}
|
||||
start, end = ClampRange(start, end, c.cfg.MaxQueryLength.Nanoseconds())
|
||||
if limit < 1 {
|
||||
limit = defaultPageSize
|
||||
}
|
||||
if limit > maximumPageSize {
|
||||
limit = maximumPageSize
|
||||
}
|
||||
|
||||
queryURL := c.cfg.ReadPathURL.JoinPath("/loki/api/v1/query_range")
|
||||
|
||||
values := url.Values{}
|
||||
values.Set("query", logQL)
|
||||
values.Set("start", fmt.Sprintf("%d", start))
|
||||
values.Set("end", fmt.Sprintf("%d", end))
|
||||
values.Set("limit", fmt.Sprintf("%d", limit))
|
||||
|
||||
queryURL.RawQuery = values.Encode()
|
||||
log.Debug("Sending query request", "query", logQL, "start", start, "end", end, "limit", limit)
|
||||
req, err := http.NewRequest(http.MethodGet,
|
||||
queryURL.String(), nil)
|
||||
if err != nil {
|
||||
return QueryRes{}, fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
c.setAuthAndTenantHeaders(req)
|
||||
|
||||
res, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return QueryRes{}, fmt.Errorf("error executing request: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := res.Body.Close(); err != nil {
|
||||
log.Warn("Failed to close response body", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
data, err := c.handleLokiResponse(log, res)
|
||||
if err != nil {
|
||||
return QueryRes{}, err
|
||||
}
|
||||
|
||||
result := QueryRes{}
|
||||
err = json.Unmarshal(data, &result)
|
||||
if err != nil {
|
||||
fmt.Println(string(data))
|
||||
return QueryRes{}, fmt.Errorf("error parsing request response: %w", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *HttpLokiClient) MaxQuerySize() int {
|
||||
return c.cfg.MaxQuerySize
|
||||
}
|
||||
|
||||
type QueryRes struct {
|
||||
Data QueryData `json:"data"`
|
||||
}
|
||||
|
||||
type QueryData struct {
|
||||
Result []Stream `json:"result"`
|
||||
}
|
||||
|
||||
func (c *HttpLokiClient) handleLokiResponse(log log.Logger, res *http.Response) ([]byte, error) {
|
||||
if res == nil {
|
||||
return nil, fmt.Errorf("response is nil")
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading request response: %w", err)
|
||||
}
|
||||
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
if len(data) > 0 {
|
||||
log.Error("Error response from Loki", "response", string(data), "status", res.StatusCode)
|
||||
} else {
|
||||
log.Error("Error response from Loki with an empty body", "status", res.StatusCode)
|
||||
}
|
||||
return nil, fmt.Errorf("received a non-200 response from loki, status: %d", res.StatusCode)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// ClampRange ensures that the time range is within the configured maximum query length.
|
||||
func ClampRange(start, end, maxTimeRange int64) (newStart int64, newEnd int64) {
|
||||
newStart, newEnd = start, end
|
||||
|
||||
if maxTimeRange != 0 && end-start > maxTimeRange {
|
||||
newStart = end - maxTimeRange
|
||||
}
|
||||
|
||||
return newStart, newEnd
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
package lokiclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/client"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
)
|
||||
|
||||
const lokiClientSpanName = "testLokiClientSpanName"
|
||||
|
||||
func TestLokiConfig(t *testing.T) {
|
||||
t.Run("test URL options", func(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
in setting.UnifiedAlertingLokiSettings
|
||||
expRead string
|
||||
expWrite string
|
||||
expErr string
|
||||
}
|
||||
|
||||
cases := []testCase{
|
||||
{
|
||||
name: "remote url only",
|
||||
in: setting.UnifiedAlertingLokiSettings{
|
||||
LokiRemoteURL: "http://url.com",
|
||||
},
|
||||
expRead: "http://url.com",
|
||||
expWrite: "http://url.com",
|
||||
},
|
||||
{
|
||||
name: "separate urls",
|
||||
in: setting.UnifiedAlertingLokiSettings{
|
||||
LokiReadURL: "http://read.url.com",
|
||||
LokiWriteURL: "http://write.url.com",
|
||||
},
|
||||
expRead: "http://read.url.com",
|
||||
expWrite: "http://write.url.com",
|
||||
},
|
||||
{
|
||||
name: "single fallback",
|
||||
in: setting.UnifiedAlertingLokiSettings{
|
||||
LokiRemoteURL: "http://url.com",
|
||||
LokiReadURL: "http://read.url.com",
|
||||
},
|
||||
expRead: "http://read.url.com",
|
||||
expWrite: "http://url.com",
|
||||
},
|
||||
{
|
||||
name: "missing read",
|
||||
in: setting.UnifiedAlertingLokiSettings{
|
||||
LokiWriteURL: "http://url.com",
|
||||
},
|
||||
expErr: "either read path URL or remote",
|
||||
},
|
||||
{
|
||||
name: "missing write",
|
||||
in: setting.UnifiedAlertingLokiSettings{
|
||||
LokiReadURL: "http://url.com",
|
||||
},
|
||||
expErr: "either write path URL or remote",
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
in: setting.UnifiedAlertingLokiSettings{
|
||||
LokiRemoteURL: "://://",
|
||||
},
|
||||
expErr: "failed to parse",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res, err := NewLokiConfig(tc.in)
|
||||
if tc.expErr != "" {
|
||||
require.ErrorContains(t, err, tc.expErr)
|
||||
} else {
|
||||
require.Equal(t, tc.expRead, res.ReadPathURL.String())
|
||||
require.Equal(t, tc.expWrite, res.WritePathURL.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("captures external labels", func(t *testing.T) {
|
||||
set := setting.UnifiedAlertingLokiSettings{
|
||||
LokiRemoteURL: "http://url.com",
|
||||
ExternalLabels: map[string]string{"a": "b"},
|
||||
}
|
||||
|
||||
res, err := NewLokiConfig(set)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, res.ExternalLabels, "a")
|
||||
})
|
||||
}
|
||||
|
||||
func TestLokiHTTPClient(t *testing.T) {
|
||||
t.Run("push formats expected data", func(t *testing.T) {
|
||||
req := NewFakeRequester()
|
||||
client := createTestLokiClient(req)
|
||||
now := time.Now().UTC()
|
||||
data := []Stream{
|
||||
{
|
||||
Stream: map[string]string{},
|
||||
Values: []Sample{
|
||||
{
|
||||
T: now,
|
||||
V: "some line",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := client.Push(context.Background(), data)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, "/loki/api/v1/push", req.LastRequest.URL.Path)
|
||||
sent := reqBody(t, req.LastRequest)
|
||||
exp := fmt.Sprintf(`{"streams": [{"stream": {}, "values": [["%d", "some line"]]}]}`, now.UnixNano())
|
||||
require.JSONEq(t, exp, sent)
|
||||
})
|
||||
|
||||
t.Run("range query", func(t *testing.T) {
|
||||
t.Run("passes along page size", func(t *testing.T) {
|
||||
req := NewFakeRequester().WithResponse(&http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(bytes.NewBufferString(`{}`)),
|
||||
ContentLength: int64(0),
|
||||
Header: make(http.Header, 0),
|
||||
})
|
||||
client := createTestLokiClient(req)
|
||||
now := time.Now().UTC().UnixNano()
|
||||
q := `{from="state-history"}`
|
||||
|
||||
_, err := client.RangeQuery(context.Background(), q, now-100, now, 1100)
|
||||
|
||||
require.NoError(t, err)
|
||||
params := req.LastRequest.URL.Query()
|
||||
require.True(t, params.Has("limit"), "query params did not contain 'limit': %#v", params)
|
||||
require.Equal(t, fmt.Sprint(1100), params.Get("limit"))
|
||||
})
|
||||
|
||||
t.Run("uses default page size if limit not provided", func(t *testing.T) {
|
||||
req := NewFakeRequester().WithResponse(&http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(bytes.NewBufferString(`{}`)),
|
||||
ContentLength: int64(0),
|
||||
Header: make(http.Header, 0),
|
||||
})
|
||||
client := createTestLokiClient(req)
|
||||
now := time.Now().UTC().UnixNano()
|
||||
q := `{from="state-history"}`
|
||||
|
||||
_, err := client.RangeQuery(context.Background(), q, now-100, now, 0)
|
||||
|
||||
require.NoError(t, err)
|
||||
params := req.LastRequest.URL.Query()
|
||||
require.True(t, params.Has("limit"), "query params did not contain 'limit': %#v", params)
|
||||
require.Equal(t, fmt.Sprint(defaultPageSize), params.Get("limit"))
|
||||
})
|
||||
|
||||
t.Run("uses default page size if limit invalid", func(t *testing.T) {
|
||||
req := NewFakeRequester().WithResponse(&http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(bytes.NewBufferString(`{}`)),
|
||||
ContentLength: int64(0),
|
||||
Header: make(http.Header, 0),
|
||||
})
|
||||
client := createTestLokiClient(req)
|
||||
now := time.Now().UTC().UnixNano()
|
||||
q := `{from="state-history"}`
|
||||
|
||||
_, err := client.RangeQuery(context.Background(), q, now-100, now, -100)
|
||||
|
||||
require.NoError(t, err)
|
||||
params := req.LastRequest.URL.Query()
|
||||
require.True(t, params.Has("limit"), "query params did not contain 'limit': %#v", params)
|
||||
require.Equal(t, fmt.Sprint(defaultPageSize), params.Get("limit"))
|
||||
})
|
||||
|
||||
t.Run("uses maximum page size if limit too big", func(t *testing.T) {
|
||||
req := NewFakeRequester().WithResponse(&http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(bytes.NewBufferString(`{}`)),
|
||||
ContentLength: int64(0),
|
||||
Header: make(http.Header, 0),
|
||||
})
|
||||
client := createTestLokiClient(req)
|
||||
now := time.Now().UTC().UnixNano()
|
||||
q := `{from="state-history"}`
|
||||
|
||||
_, err := client.RangeQuery(context.Background(), q, now-100, now, maximumPageSize+1000)
|
||||
|
||||
require.NoError(t, err)
|
||||
params := req.LastRequest.URL.Query()
|
||||
require.True(t, params.Has("limit"), "query params did not contain 'limit': %#v", params)
|
||||
require.Equal(t, fmt.Sprint(maximumPageSize), params.Get("limit"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// This function can be used for local testing, just remove the skip call.
|
||||
func TestLokiHTTPClient_Manual(t *testing.T) {
|
||||
t.Skip()
|
||||
|
||||
t.Run("smoke test pinging Loki", func(t *testing.T) {
|
||||
url, err := url.Parse("https://logs-prod-eu-west-0.grafana.net")
|
||||
require.NoError(t, err)
|
||||
|
||||
metrics := metrics.NewHistorianMetrics(prometheus.NewRegistry(), metrics.Subsystem)
|
||||
client := NewLokiClient(LokiConfig{
|
||||
ReadPathURL: url,
|
||||
WritePathURL: url,
|
||||
Encoder: JsonEncoder{},
|
||||
}, NewRequester(), metrics.BytesWritten, metrics.WriteDuration, log.NewNopLogger(), tracing.InitializeTracerForTest(), lokiClientSpanName)
|
||||
|
||||
// Unauthorized request should fail against Grafana Cloud.
|
||||
err = client.Ping(context.Background())
|
||||
require.Error(t, err)
|
||||
|
||||
client.cfg.BasicAuthUser = "<your_username>"
|
||||
client.cfg.BasicAuthPassword = "<your_password>"
|
||||
|
||||
// When running on prem, you might need to set the tenant id,
|
||||
// so the x-scope-orgid header is set.
|
||||
// client.cfg.TenantID = "<your_tenant_id>"
|
||||
|
||||
// Authorized request should not fail against Grafana Cloud.
|
||||
err = client.Ping(context.Background())
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("smoke test range querying Loki", func(t *testing.T) {
|
||||
url, err := url.Parse("https://logs-prod-eu-west-0.grafana.net")
|
||||
require.NoError(t, err)
|
||||
|
||||
metrics := metrics.NewHistorianMetrics(prometheus.NewRegistry(), metrics.Subsystem)
|
||||
client := NewLokiClient(LokiConfig{
|
||||
ReadPathURL: url,
|
||||
WritePathURL: url,
|
||||
BasicAuthUser: "<your_username>",
|
||||
BasicAuthPassword: "<your_password>",
|
||||
Encoder: JsonEncoder{},
|
||||
}, NewRequester(), metrics.BytesWritten, metrics.WriteDuration, log.NewNopLogger(), tracing.InitializeTracerForTest(), lokiClientSpanName)
|
||||
|
||||
// When running on prem, you might need to set the tenant id,
|
||||
// so the x-scope-orgid header is set.
|
||||
// client.cfg.TenantID = "<your_tenant_id>"
|
||||
|
||||
logQL := `{probe="Paris"}`
|
||||
|
||||
// Define the query time range
|
||||
start := time.Now().Add(-30 * time.Minute).UnixNano()
|
||||
end := time.Now().UnixNano()
|
||||
|
||||
// Authorized request should not fail against Grafana Cloud.
|
||||
res, err := client.RangeQuery(context.Background(), logQL, start, end, defaultPageSize)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRow(t *testing.T) {
|
||||
t.Run("marshal", func(t *testing.T) {
|
||||
row := Sample{
|
||||
T: time.Unix(0, 1234),
|
||||
V: "some sample",
|
||||
}
|
||||
|
||||
jsn, err := json.Marshal(&row)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, `["1234", "some sample"]`, string(jsn))
|
||||
})
|
||||
|
||||
t.Run("unmarshal", func(t *testing.T) {
|
||||
jsn := []byte(`["1234", "some sample"]`)
|
||||
|
||||
row := Sample{}
|
||||
err := json.Unmarshal(jsn, &row)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1234), row.T.UnixNano())
|
||||
require.Equal(t, "some sample", row.V)
|
||||
})
|
||||
|
||||
t.Run("unmarshal invalid", func(t *testing.T) {
|
||||
jsn := []byte(`{"key": "wrong shape"}`)
|
||||
|
||||
row := Sample{}
|
||||
err := json.Unmarshal(jsn, &row)
|
||||
|
||||
require.ErrorContains(t, err, "failed to deserialize sample")
|
||||
})
|
||||
|
||||
t.Run("unmarshal bad timestamp", func(t *testing.T) {
|
||||
jsn := []byte(`["not-unix-nano", "some sample"]`)
|
||||
|
||||
row := Sample{}
|
||||
err := json.Unmarshal(jsn, &row)
|
||||
|
||||
require.ErrorContains(t, err, "timestamp in Loki sample")
|
||||
})
|
||||
}
|
||||
|
||||
func TestStream(t *testing.T) {
|
||||
t.Run("marshal", func(t *testing.T) {
|
||||
stream := Stream{
|
||||
Stream: map[string]string{"a": "b"},
|
||||
Values: []Sample{
|
||||
{T: time.Unix(0, 1), V: "one"},
|
||||
{T: time.Unix(0, 2), V: "two"},
|
||||
},
|
||||
}
|
||||
|
||||
jsn, err := json.Marshal(stream)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(
|
||||
t,
|
||||
`{"stream": {"a": "b"}, "values": [["1", "one"], ["2", "two"]]}`,
|
||||
string(jsn),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func TestClampRange(t *testing.T) {
|
||||
tc := []struct {
|
||||
name string
|
||||
oldRange []int64
|
||||
max int64
|
||||
newRange []int64
|
||||
}{
|
||||
{
|
||||
name: "clamps start value if max is smaller than range",
|
||||
oldRange: []int64{5, 10},
|
||||
max: 1,
|
||||
newRange: []int64{9, 10},
|
||||
},
|
||||
{
|
||||
name: "returns same values if max is greater than range",
|
||||
oldRange: []int64{5, 10},
|
||||
max: 20,
|
||||
newRange: []int64{5, 10},
|
||||
},
|
||||
{
|
||||
name: "returns same values if max is equal to range",
|
||||
oldRange: []int64{5, 10},
|
||||
max: 5,
|
||||
newRange: []int64{5, 10},
|
||||
},
|
||||
{
|
||||
name: "returns same values if max is zero",
|
||||
oldRange: []int64{5, 10},
|
||||
max: 0,
|
||||
newRange: []int64{5, 10},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range tc {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
start, end := ClampRange(c.oldRange[0], c.oldRange[1], c.max)
|
||||
|
||||
require.Equal(t, c.newRange[0], start)
|
||||
require.Equal(t, c.newRange[1], end)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createTestLokiClient(req client.Requester) *HttpLokiClient {
|
||||
url, _ := url.Parse("http://some.url")
|
||||
cfg := LokiConfig{
|
||||
WritePathURL: url,
|
||||
ReadPathURL: url,
|
||||
Encoder: JsonEncoder{},
|
||||
}
|
||||
met := metrics.NewHistorianMetrics(prometheus.NewRegistry(), metrics.Subsystem)
|
||||
return NewLokiClient(cfg, req, met.BytesWritten, met.WriteDuration, log.NewNopLogger(), tracing.InitializeTracerForTest(), lokiClientSpanName)
|
||||
}
|
||||
|
||||
func reqBody(t *testing.T, req *http.Request) string {
|
||||
t.Helper()
|
||||
|
||||
defer func() {
|
||||
_ = req.Body.Close()
|
||||
}()
|
||||
byt, err := io.ReadAll(req.Body)
|
||||
require.NoError(t, err)
|
||||
return string(byt)
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package lokiclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/golang/snappy"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/loki/logproto"
|
||||
)
|
||||
|
||||
type JsonEncoder struct{}
|
||||
|
||||
func (e JsonEncoder) encode(s []Stream) ([]byte, error) {
|
||||
body := struct {
|
||||
Streams []Stream `json:"streams"`
|
||||
}{Streams: s}
|
||||
enc, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to serialize Loki payload: %w", err)
|
||||
}
|
||||
return enc, nil
|
||||
}
|
||||
|
||||
func (e JsonEncoder) headers() map[string]string {
|
||||
return map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
}
|
||||
|
||||
type SnappyProtoEncoder struct{}
|
||||
|
||||
func (e SnappyProtoEncoder) encode(s []Stream) ([]byte, error) {
|
||||
body := logproto.PushRequest{
|
||||
Streams: make([]logproto.Stream, 0, len(s)),
|
||||
}
|
||||
|
||||
for _, str := range s {
|
||||
entries := make([]logproto.Entry, 0, len(str.Values))
|
||||
for _, sample := range str.Values {
|
||||
entries = append(entries, logproto.Entry{
|
||||
Timestamp: sample.T,
|
||||
Line: sample.V,
|
||||
})
|
||||
}
|
||||
body.Streams = append(body.Streams, logproto.Stream{
|
||||
Labels: labelsMapToString(str.Stream, ""),
|
||||
Entries: entries,
|
||||
// Hash seems to be mainly used for query responses. Promtail does not seem to calculate this field on push.
|
||||
})
|
||||
}
|
||||
|
||||
buf, err := proto.Marshal(&body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to serialize Loki payload to proto: %w", err)
|
||||
}
|
||||
buf = snappy.Encode(nil, buf)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (e SnappyProtoEncoder) headers() map[string]string {
|
||||
return map[string]string{
|
||||
"Content-Type": "application/x-protobuf",
|
||||
"Content-Encoding": "snappy",
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from promtail.
|
||||
// Modified slightly to work in terms of plain map[string]string to avoid some unnecessary copies and type casts.
|
||||
// TODO: pkg/components/loki/lokihttp/batch.go contains an older (loki 2.7.4 released) version of this.
|
||||
// TODO: Consider replacing that one, with this one.
|
||||
func labelsMapToString(ls map[string]string, without model.LabelName) string {
|
||||
var b strings.Builder
|
||||
totalSize := 2
|
||||
lstrs := make([]string, 0, len(ls))
|
||||
|
||||
for l, v := range ls {
|
||||
if l == string(without) {
|
||||
continue
|
||||
}
|
||||
|
||||
lstrs = append(lstrs, l)
|
||||
// guess size increase: 2 for `, ` between labels and 3 for the `=` and quotes around label value
|
||||
totalSize += len(l) + 2 + len(v) + 3
|
||||
}
|
||||
|
||||
b.Grow(totalSize)
|
||||
b.WriteByte('{')
|
||||
slices.Sort(lstrs)
|
||||
for i, l := range lstrs {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
|
||||
b.WriteString(l)
|
||||
b.WriteString(`=`)
|
||||
b.WriteString(strconv.Quote(ls[l]))
|
||||
}
|
||||
b.WriteByte('}')
|
||||
|
||||
return b.String()
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package lokiclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type FakeRequester struct {
|
||||
LastRequest *http.Request
|
||||
Resp *http.Response
|
||||
}
|
||||
|
||||
func NewFakeRequester() *FakeRequester {
|
||||
return &FakeRequester{
|
||||
Resp: &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(bytes.NewBufferString("")),
|
||||
ContentLength: int64(0),
|
||||
Header: make(http.Header, 0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeRequester) WithResponse(resp *http.Response) *FakeRequester {
|
||||
f.Resp = resp
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FakeRequester) Do(req *http.Request) (*http.Response, error) {
|
||||
f.LastRequest = req
|
||||
f.Resp.Request = req // Not concurrency-safe!
|
||||
return f.Resp, nil
|
||||
}
|
||||
|
||||
func BadResponse() *http.Response {
|
||||
return &http.Response{
|
||||
Status: "400 Bad Request",
|
||||
StatusCode: http.StatusBadRequest,
|
||||
Body: io.NopCloser(bytes.NewBufferString("")),
|
||||
ContentLength: int64(0),
|
||||
Header: make(http.Header, 0),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package lokiconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/grafana/alerting/lokiclient"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func NewLokiConfig(cfg setting.UnifiedAlertingLokiSettings) (lokiclient.LokiConfig, error) {
|
||||
read, write := cfg.LokiReadURL, cfg.LokiWriteURL
|
||||
if read == "" {
|
||||
read = cfg.LokiRemoteURL
|
||||
}
|
||||
if write == "" {
|
||||
write = cfg.LokiRemoteURL
|
||||
}
|
||||
|
||||
if read == "" {
|
||||
return lokiclient.LokiConfig{}, fmt.Errorf("either read path URL or remote Loki URL must be provided")
|
||||
}
|
||||
if write == "" {
|
||||
return lokiclient.LokiConfig{}, fmt.Errorf("either write path URL or remote Loki URL must be provided")
|
||||
}
|
||||
|
||||
readURL, err := url.Parse(read)
|
||||
if err != nil {
|
||||
return lokiclient.LokiConfig{}, fmt.Errorf("failed to parse loki remote read URL: %w", err)
|
||||
}
|
||||
writeURL, err := url.Parse(write)
|
||||
if err != nil {
|
||||
return lokiclient.LokiConfig{}, fmt.Errorf("failed to parse loki remote write URL: %w", err)
|
||||
}
|
||||
|
||||
return lokiclient.LokiConfig{
|
||||
ReadPathURL: readURL,
|
||||
WritePathURL: writeURL,
|
||||
BasicAuthUser: cfg.LokiBasicAuthUsername,
|
||||
BasicAuthPassword: cfg.LokiBasicAuthPassword,
|
||||
TenantID: cfg.LokiTenantID,
|
||||
ExternalLabels: cfg.ExternalLabels,
|
||||
MaxQueryLength: cfg.LokiMaxQueryLength,
|
||||
MaxQuerySize: cfg.LokiMaxQuerySize,
|
||||
// Snappy-compressed protobuf is the default, same goes for Promtail.
|
||||
Encoder: lokiclient.SnappyProtoEncoder{},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package lokiconfig
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLokiConfig(t *testing.T) {
|
||||
t.Run("test URL options", func(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
in setting.UnifiedAlertingLokiSettings
|
||||
expRead string
|
||||
expWrite string
|
||||
expErr string
|
||||
}
|
||||
|
||||
cases := []testCase{
|
||||
{
|
||||
name: "remote url only",
|
||||
in: setting.UnifiedAlertingLokiSettings{
|
||||
LokiRemoteURL: "http://url.com",
|
||||
},
|
||||
expRead: "http://url.com",
|
||||
expWrite: "http://url.com",
|
||||
},
|
||||
{
|
||||
name: "separate urls",
|
||||
in: setting.UnifiedAlertingLokiSettings{
|
||||
LokiReadURL: "http://read.url.com",
|
||||
LokiWriteURL: "http://write.url.com",
|
||||
},
|
||||
expRead: "http://read.url.com",
|
||||
expWrite: "http://write.url.com",
|
||||
},
|
||||
{
|
||||
name: "single fallback",
|
||||
in: setting.UnifiedAlertingLokiSettings{
|
||||
LokiRemoteURL: "http://url.com",
|
||||
LokiReadURL: "http://read.url.com",
|
||||
},
|
||||
expRead: "http://read.url.com",
|
||||
expWrite: "http://url.com",
|
||||
},
|
||||
{
|
||||
name: "missing read",
|
||||
in: setting.UnifiedAlertingLokiSettings{
|
||||
LokiWriteURL: "http://url.com",
|
||||
},
|
||||
expErr: "either read path URL or remote",
|
||||
},
|
||||
{
|
||||
name: "missing write",
|
||||
in: setting.UnifiedAlertingLokiSettings{
|
||||
LokiReadURL: "http://url.com",
|
||||
},
|
||||
expErr: "either write path URL or remote",
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
in: setting.UnifiedAlertingLokiSettings{
|
||||
LokiRemoteURL: "://://",
|
||||
},
|
||||
expErr: "failed to parse",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res, err := NewLokiConfig(tc.in)
|
||||
if tc.expErr != "" {
|
||||
require.ErrorContains(t, err, tc.expErr)
|
||||
} else {
|
||||
require.Equal(t, tc.expRead, res.ReadPathURL.String())
|
||||
require.Equal(t, tc.expWrite, res.WritePathURL.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("captures external labels", func(t *testing.T) {
|
||||
set := setting.UnifiedAlertingLokiSettings{
|
||||
LokiRemoteURL: "http://url.com",
|
||||
ExternalLabels: map[string]string{"a": "b"},
|
||||
}
|
||||
|
||||
res, err := NewLokiConfig(set)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, res.ExternalLabels, "a")
|
||||
})
|
||||
}
|
||||
@@ -7,8 +7,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/grafana/alerting/lokiclient"
|
||||
notificationhistorian "github.com/grafana/alerting/notificationhistorian"
|
||||
"github.com/grafana/alerting/notify/nfstatus"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/lokiclient"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/lokiconfig"
|
||||
"github.com/prometheus/alertmanager/featurecontrol"
|
||||
"github.com/prometheus/alertmanager/matchers/compat"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -693,7 +695,7 @@ func configureHistorianBackend(
|
||||
return historian.NewAnnotationBackend(annotationBackendLogger, store, rs, met, ac), nil
|
||||
}
|
||||
if backend == historian.BackendTypeLoki {
|
||||
lcfg, err := lokiclient.NewLokiConfig(cfg.LokiSettings)
|
||||
lcfg, err := lokiconfig.NewLokiConfig(cfg.LokiSettings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid remote loki configuration: %w", err)
|
||||
}
|
||||
@@ -746,13 +748,13 @@ func configureNotificationHistorian(
|
||||
}
|
||||
|
||||
met.Info.Set(1)
|
||||
lcfg, err := lokiclient.NewLokiConfig(cfg.LokiSettings)
|
||||
lcfg, err := lokiconfig.NewLokiConfig(cfg.LokiSettings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid remote loki configuration: %w", err)
|
||||
}
|
||||
req := lokiclient.NewRequester()
|
||||
logger := log.New("ngalert.notifier.historian").FromContext(ctx)
|
||||
notificationHistorian := notifier.NewNotificationHistorian(logger, lcfg, req, met, tracer)
|
||||
notificationHistorian := notificationhistorian.NewNotificationHistorian(logger, lcfg, req, met.BytesWritten, met.WriteDuration, met.WritesTotal, met.WritesFailed, tracer)
|
||||
|
||||
testConnCtx, cancelFunc := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancelFunc()
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
package notifier
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
alertingModels "github.com/grafana/alerting/models"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/client"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/lokiclient"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
prometheusModel "github.com/prometheus/common/model"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const LokiClientSpanName = "ngalert.notification-historian.client"
|
||||
const NotificationHistoryWriteTimeout = time.Minute
|
||||
const NotificationHistoryKey = "from"
|
||||
const NotificationHistoryLabelValue = "notify-history"
|
||||
|
||||
type NotificationHistoryLokiEntry struct {
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
Receiver string `json:"receiver"`
|
||||
Status string `json:"status"`
|
||||
GroupLabels map[string]string `json:"groupLabels"`
|
||||
Alerts []NotificationHistoryLokiEntryAlert `json:"alerts"`
|
||||
Retry bool `json:"retry"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Duration int64 `json:"duration"`
|
||||
}
|
||||
|
||||
type NotificationHistoryLokiEntryAlert struct {
|
||||
Status string `json:"status"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
Annotations map[string]string `json:"annotations"`
|
||||
StartsAt time.Time `json:"startsAt"`
|
||||
EndsAt time.Time `json:"endsAt"`
|
||||
RuleUID string `json:"ruleUID"`
|
||||
}
|
||||
|
||||
type remoteLokiClient interface {
|
||||
Ping(context.Context) error
|
||||
Push(context.Context, []lokiclient.Stream) error
|
||||
}
|
||||
|
||||
type NotificationHistorian struct {
|
||||
client remoteLokiClient
|
||||
externalLabels map[string]string
|
||||
metrics *metrics.NotificationHistorian
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func NewNotificationHistorian(logger log.Logger, cfg lokiclient.LokiConfig, req client.Requester, metrics *metrics.NotificationHistorian, tracer tracing.Tracer) *NotificationHistorian {
|
||||
return &NotificationHistorian{
|
||||
client: lokiclient.NewLokiClient(cfg, req, metrics.BytesWritten, metrics.WriteDuration, logger, tracer, LokiClientSpanName),
|
||||
externalLabels: cfg.ExternalLabels,
|
||||
metrics: metrics,
|
||||
log: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *NotificationHistorian) TestConnection(ctx context.Context) error {
|
||||
return h.client.Ping(ctx)
|
||||
}
|
||||
|
||||
func (h *NotificationHistorian) Record(ctx context.Context, alerts []*types.Alert, retry bool, notificationErr error, duration time.Duration) <-chan error {
|
||||
stream, err := h.prepareStream(ctx, alerts, retry, notificationErr, duration)
|
||||
logger := h.log.FromContext(ctx)
|
||||
errCh := make(chan error, 1)
|
||||
if err != nil {
|
||||
logger.Error("Failed to convert notification history to stream", "error", err)
|
||||
errCh <- fmt.Errorf("failed to convert notification history to stream: %w", err)
|
||||
close(errCh)
|
||||
return errCh
|
||||
}
|
||||
|
||||
// This is a new background job, so let's create a new context for it.
|
||||
// We want it to be isolated, i.e. we don't want grafana shutdowns to interrupt this work
|
||||
// immediately but rather try to flush writes.
|
||||
// This also prevents timeouts or other lingering objects (like transactions) from being
|
||||
// incorrectly propagated here from other areas.
|
||||
writeCtx := context.Background()
|
||||
writeCtx, cancel := context.WithTimeout(writeCtx, NotificationHistoryWriteTimeout)
|
||||
writeCtx = trace.ContextWithSpan(writeCtx, trace.SpanFromContext(ctx))
|
||||
|
||||
go func(ctx context.Context) {
|
||||
defer cancel()
|
||||
defer close(errCh)
|
||||
logger := h.log.FromContext(ctx)
|
||||
logger.Debug("Saving notification history")
|
||||
h.metrics.WritesTotal.Inc()
|
||||
|
||||
if err := h.recordStream(ctx, stream, logger); err != nil {
|
||||
logger.Error("Failed to save notification history", "error", err)
|
||||
h.metrics.WritesFailed.Inc()
|
||||
errCh <- fmt.Errorf("failed to save notification history: %w", err)
|
||||
}
|
||||
}(writeCtx)
|
||||
return errCh
|
||||
}
|
||||
|
||||
func (h *NotificationHistorian) prepareStream(ctx context.Context, alerts []*types.Alert, retry bool, notificationErr error, duration time.Duration) (lokiclient.Stream, error) {
|
||||
receiverName, ok := notify.ReceiverName(ctx)
|
||||
if !ok {
|
||||
return lokiclient.Stream{}, fmt.Errorf("receiver name not found in context")
|
||||
}
|
||||
groupLabels, ok := notify.GroupLabels(ctx)
|
||||
if !ok {
|
||||
return lokiclient.Stream{}, fmt.Errorf("group labels not found in context")
|
||||
}
|
||||
now, ok := notify.Now(ctx)
|
||||
if !ok {
|
||||
return lokiclient.Stream{}, fmt.Errorf("now not found in context")
|
||||
}
|
||||
|
||||
entryAlerts := make([]NotificationHistoryLokiEntryAlert, len(alerts))
|
||||
for i, alert := range alerts {
|
||||
labels := prepareLabels(alert.Labels)
|
||||
annotations := prepareLabels(alert.Annotations)
|
||||
entryAlerts[i] = NotificationHistoryLokiEntryAlert{
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
Status: string(alert.StatusAt(now)),
|
||||
StartsAt: alert.StartsAt,
|
||||
EndsAt: alert.EndsAt,
|
||||
RuleUID: string(alert.Labels[alertingModels.RuleUIDLabel]),
|
||||
}
|
||||
}
|
||||
|
||||
notificationErrStr := ""
|
||||
if notificationErr != nil {
|
||||
notificationErrStr = notificationErr.Error()
|
||||
}
|
||||
|
||||
entry := NotificationHistoryLokiEntry{
|
||||
SchemaVersion: 1,
|
||||
Receiver: receiverName,
|
||||
Status: string(types.Alerts(alerts...).StatusAt(now)),
|
||||
GroupLabels: prepareLabels(groupLabels),
|
||||
Alerts: entryAlerts,
|
||||
Retry: retry,
|
||||
Error: notificationErrStr,
|
||||
Duration: duration.Milliseconds(),
|
||||
}
|
||||
|
||||
entryJSON, err := json.Marshal(entry)
|
||||
if err != nil {
|
||||
return lokiclient.Stream{}, err
|
||||
}
|
||||
|
||||
streamLabels := make(map[string]string)
|
||||
streamLabels[NotificationHistoryKey] = NotificationHistoryLabelValue
|
||||
for k, v := range h.externalLabels {
|
||||
streamLabels[k] = v
|
||||
}
|
||||
|
||||
return lokiclient.Stream{
|
||||
Stream: streamLabels,
|
||||
Values: []lokiclient.Sample{
|
||||
{
|
||||
T: now,
|
||||
V: string(entryJSON),
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *NotificationHistorian) recordStream(ctx context.Context, stream lokiclient.Stream, logger log.Logger) error {
|
||||
if err := h.client.Push(ctx, []lokiclient.Stream{stream}); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Debug("Done saving notification history")
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareLabels(labels prometheusModel.LabelSet) map[string]string {
|
||||
result := make(map[string]string)
|
||||
for k, v := range labels {
|
||||
// Remove private labels
|
||||
if !strings.HasPrefix(string(k), "__") && !strings.HasSuffix(string(k), "__") {
|
||||
result[string(k)] = string(v)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package notifier
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
alertingModels "github.com/grafana/alerting/models"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/client"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/lokiclient"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var testNow = time.Date(2025, time.July, 15, 16, 55, 0, 0, time.UTC)
|
||||
var testAlerts = []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "Alert1", alertingModels.RuleUIDLabel: "testRuleUID"},
|
||||
Annotations: model.LabelSet{"foo": "bar", "__private__": "baz"},
|
||||
StartsAt: testNow,
|
||||
EndsAt: testNow,
|
||||
GeneratorURL: "http://localhost/test",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestRecord(t *testing.T) {
|
||||
t.Run("write notification history to Loki", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
retry bool
|
||||
notificationErr error
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"successful notification",
|
||||
false,
|
||||
nil,
|
||||
"{\"streams\":[{\"stream\":{\"externalLabelKey\":\"externalLabelValue\",\"from\":\"notify-history\"},\"values\":[[\"1752598500000000000\",\"{\\\"schemaVersion\\\":1,\\\"receiver\\\":\\\"testReceiverName\\\",\\\"status\\\":\\\"resolved\\\",\\\"groupLabels\\\":{\\\"foo\\\":\\\"bar\\\"},\\\"alerts\\\":[{\\\"status\\\":\\\"resolved\\\",\\\"labels\\\":{\\\"alertname\\\":\\\"Alert1\\\"},\\\"annotations\\\":{\\\"foo\\\":\\\"bar\\\"},\\\"startsAt\\\":\\\"2025-07-15T16:55:00Z\\\",\\\"endsAt\\\":\\\"2025-07-15T16:55:00Z\\\",\\\"ruleUID\\\":\\\"testRuleUID\\\"}],\\\"retry\\\":false,\\\"duration\\\":1000}\"]]}]}",
|
||||
},
|
||||
{
|
||||
"failed notification",
|
||||
true,
|
||||
errors.New("test notification error"),
|
||||
"{\"streams\":[{\"stream\":{\"externalLabelKey\":\"externalLabelValue\",\"from\":\"notify-history\"},\"values\":[[\"1752598500000000000\",\"{\\\"schemaVersion\\\":1,\\\"receiver\\\":\\\"testReceiverName\\\",\\\"status\\\":\\\"resolved\\\",\\\"groupLabels\\\":{\\\"foo\\\":\\\"bar\\\"},\\\"alerts\\\":[{\\\"status\\\":\\\"resolved\\\",\\\"labels\\\":{\\\"alertname\\\":\\\"Alert1\\\"},\\\"annotations\\\":{\\\"foo\\\":\\\"bar\\\"},\\\"startsAt\\\":\\\"2025-07-15T16:55:00Z\\\",\\\"endsAt\\\":\\\"2025-07-15T16:55:00Z\\\",\\\"ruleUID\\\":\\\"testRuleUID\\\"}],\\\"retry\\\":true,\\\"error\\\":\\\"test notification error\\\",\\\"duration\\\":1000}\"]]}]}",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := lokiclient.NewFakeRequester()
|
||||
met := metrics.NewNotificationHistorianMetrics(prometheus.NewRegistry())
|
||||
h := createTestNotificationHistorian(req, met)
|
||||
|
||||
err := <-h.Record(recordCtx(), testAlerts, tc.retry, tc.notificationErr, time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
reqBody, err := io.ReadAll(req.LastRequest.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, string(reqBody))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("emits expected write metrics", func(t *testing.T) {
|
||||
reg := prometheus.NewRegistry()
|
||||
met := metrics.NewNotificationHistorianMetrics(reg)
|
||||
goodHistorian := createTestNotificationHistorian(lokiclient.NewFakeRequester(), met)
|
||||
badHistorian := createTestNotificationHistorian(lokiclient.NewFakeRequester().WithResponse(lokiclient.BadResponse()), met)
|
||||
|
||||
<-goodHistorian.Record(recordCtx(), testAlerts, false, nil, time.Second)
|
||||
<-badHistorian.Record(recordCtx(), testAlerts, false, nil, time.Second)
|
||||
|
||||
exp := bytes.NewBufferString(`
|
||||
# HELP grafana_alerting_notification_history_writes_failed_total The total number of failed writes of notification history batches.
|
||||
# TYPE grafana_alerting_notification_history_writes_failed_total counter
|
||||
grafana_alerting_notification_history_writes_failed_total 1
|
||||
# HELP grafana_alerting_notification_history_writes_total The total number of notification history batches that were attempted to be written.
|
||||
# TYPE grafana_alerting_notification_history_writes_total counter
|
||||
grafana_alerting_notification_history_writes_total 2
|
||||
`)
|
||||
err := testutil.GatherAndCompare(reg, exp,
|
||||
"grafana_alerting_notification_history_writes_total",
|
||||
"grafana_alerting_notification_history_writes_failed_total",
|
||||
)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("returns error when context is missing required fields", func(t *testing.T) {
|
||||
req := lokiclient.NewFakeRequester()
|
||||
met := metrics.NewNotificationHistorianMetrics(prometheus.NewRegistry())
|
||||
h := createTestNotificationHistorian(req, met)
|
||||
|
||||
err := <-h.Record(context.Background(), testAlerts, false, nil, time.Second)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func createTestNotificationHistorian(req client.Requester, met *metrics.NotificationHistorian) *NotificationHistorian {
|
||||
writePathURL, _ := url.Parse("http://some.url")
|
||||
cfg := lokiclient.LokiConfig{
|
||||
WritePathURL: writePathURL,
|
||||
ExternalLabels: map[string]string{"externalLabelKey": "externalLabelValue"},
|
||||
Encoder: lokiclient.JsonEncoder{},
|
||||
}
|
||||
tracer := tracing.InitializeTracerForTest()
|
||||
return NewNotificationHistorian(log.NewNopLogger(), cfg, req, met, tracer)
|
||||
}
|
||||
|
||||
func recordCtx() context.Context {
|
||||
ctx := notify.WithReceiverName(context.Background(), "testReceiverName")
|
||||
ctx = notify.WithGroupLabels(ctx, model.LabelSet{"foo": "bar"})
|
||||
ctx = notify.WithNow(ctx, testNow)
|
||||
return ctx
|
||||
}
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"time"
|
||||
|
||||
httptransport "github.com/go-openapi/runtime/client"
|
||||
"github.com/grafana/alerting/client"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/client"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
"github.com/grafana/grafana/pkg/util/httpclient"
|
||||
amclient "github.com/prometheus/alertmanager/api/v2/client"
|
||||
|
||||
@@ -14,10 +14,10 @@ import (
|
||||
|
||||
alertingNotify "github.com/grafana/alerting/notify"
|
||||
|
||||
"github.com/grafana/alerting/client"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/client"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
"github.com/grafana/grafana/pkg/util/httpclient"
|
||||
)
|
||||
|
||||
@@ -12,16 +12,16 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/grafana/alerting/lokiclient"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/lokiclient"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/grafana/alerting/client"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/client"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
|
||||
@@ -13,13 +13,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/alerting/lokiclient"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/lokiclient"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/alerting/client"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
@@ -27,7 +28,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
rulesAuthz "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
acfakes "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/client"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
|
||||
Reference in New Issue
Block a user