generated from TrueCloudLab/basic
Compare commits
No commits in common. "master" and "master" have entirely different histories.
32 changed files with 151 additions and 1181 deletions
|
@ -1,21 +0,0 @@
|
|||
name: DCO action
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
dco:
|
||||
name: DCO
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.22'
|
||||
|
||||
- name: Run commit format checker
|
||||
uses: https://git.frostfs.info/TrueCloudLab/dco-go@v3
|
||||
with:
|
||||
from: 'origin/${{ github.event.pull_request.base.ref }}'
|
|
@ -1,73 +0,0 @@
|
|||
name: Tests and linters
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.22'
|
||||
cache: true
|
||||
|
||||
- name: Install linters
|
||||
run: make lint-install
|
||||
|
||||
- name: Run linters
|
||||
run: make lint
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go_versions: [ '1.21', '1.22' ]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '${{ matrix.go_versions }}'
|
||||
cache: true
|
||||
|
||||
- name: Run tests
|
||||
run: make test
|
||||
|
||||
tests-race:
|
||||
name: Tests with -race
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.22'
|
||||
cache: true
|
||||
|
||||
- name: Run tests
|
||||
run: go test ./... -count=1 -race
|
||||
|
||||
staticcheck:
|
||||
name: Staticcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.22'
|
||||
cache: true
|
||||
|
||||
- name: Install staticcheck
|
||||
run: make staticcheck-install
|
||||
|
||||
- name: Run staticcheck
|
||||
run: make staticcheck-run
|
11
.gitlint
Normal file
11
.gitlint
Normal file
|
@ -0,0 +1,11 @@
|
|||
[general]
|
||||
fail-without-commits=True
|
||||
regex-style-search=True
|
||||
contrib=CC1
|
||||
|
||||
[title-match-regex]
|
||||
regex=^\[\#[0-9Xx]+\]\s
|
||||
|
||||
[ignore-by-title]
|
||||
regex=^Release(.*)
|
||||
ignore=title-match-regex
|
|
@ -25,7 +25,7 @@ linters-settings:
|
|||
# report about shadowed variables
|
||||
check-shadowing: false
|
||||
staticcheck:
|
||||
checks: ["all"]
|
||||
checks: ["all", "-SA1019"] # TODO Enable SA1019 after deprecated warning are fixed.
|
||||
funlen:
|
||||
lines: 80 # default 60
|
||||
statements: 60 # default 40
|
||||
|
@ -52,7 +52,7 @@ linters:
|
|||
- durationcheck
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
- gofumpt
|
||||
- gofmt
|
||||
- goimports
|
||||
- misspell
|
||||
- predeclared
|
||||
|
|
|
@ -2,6 +2,13 @@ ci:
|
|||
autofix_prs: false
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/jorisroovers/gitlint
|
||||
rev: v0.19.1
|
||||
hooks:
|
||||
- id: gitlint
|
||||
stages: [commit-msg]
|
||||
- id: gitlint-ci
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
|
@ -24,7 +31,7 @@ repos:
|
|||
- id: shellcheck
|
||||
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.59.1
|
||||
rev: v1.51.2
|
||||
hooks:
|
||||
- id: golangci-lint
|
||||
|
||||
|
@ -32,7 +39,7 @@ repos:
|
|||
hooks:
|
||||
- id: go-unit-tests
|
||||
name: go unit tests
|
||||
entry: make test GOFLAGS=''
|
||||
entry: make test
|
||||
pass_filenames: false
|
||||
types: [go]
|
||||
language: system
|
||||
|
|
201
LICENSE
201
LICENSE
|
@ -1,201 +0,0 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
58
Makefile
Executable file → Normal file
58
Makefile
Executable file → Normal file
|
@ -1,64 +1,14 @@
|
|||
#!/usr/bin/make -f
|
||||
SHELL = bash
|
||||
|
||||
TRUECLOUDLAB_LINT_VERSION ?= 0.0.3
|
||||
TMP_DIR := .cache
|
||||
OUTPUT_LINT_DIR ?= $(shell pwd)/bin
|
||||
LINT_VERSION ?= 1.59.1
|
||||
LINT_DIR = $(OUTPUT_LINT_DIR)/golangci-lint-$(LINT_VERSION)-v$(TRUECLOUDLAB_LINT_VERSION)
|
||||
|
||||
# Run all code formatters
|
||||
fmts: fmt imports
|
||||
|
||||
# Reformat code
|
||||
fmt:
|
||||
@echo "⇒ Processing gofmt check"
|
||||
@gofumpt -s -w .
|
||||
|
||||
# Reformat imports
|
||||
imports:
|
||||
@echo "⇒ Processing goimports check"
|
||||
@goimports -w .
|
||||
|
||||
# Run Unit Test with go test
|
||||
test: GOFLAGS ?= "-count=1"
|
||||
test:
|
||||
@echo "⇒ Running go test"
|
||||
@GOFLAGS="$(GOFLAGS)" go test ./...
|
||||
|
||||
# Activate pre-commit hooks
|
||||
pre-commit:
|
||||
pre-commit install -t pre-commit -t commit-msg
|
||||
|
||||
# Deactivate pre-commit hooks
|
||||
unpre-commit:
|
||||
pre-commit uninstall -t pre-commit -t commit-msg
|
||||
|
||||
pre-commit-run:
|
||||
@pre-commit run -a --hook-stage manual
|
||||
|
||||
# Install linters
|
||||
lint-install:
|
||||
@mkdir -p $(TMP_DIR)
|
||||
@rm -rf $(TMP_DIR)/linters
|
||||
@git -c advice.detachedHead=false clone --branch v$(TRUECLOUDLAB_LINT_VERSION) https://git.frostfs.info/TrueCloudLab/linters.git $(TMP_DIR)/linters
|
||||
@make -C $(TMP_DIR)/linters lib CGO_ENABLED=1 OUT_DIR=$(OUTPUT_LINT_DIR)
|
||||
@rm -rf $(TMP_DIR)/linters
|
||||
@rmdir $(TMP_DIR) 2>/dev/null || true
|
||||
@CGO_ENABLED=1 GOBIN=$(LINT_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v$(LINT_VERSION)
|
||||
@go test ./... -count=1
|
||||
|
||||
# Run linters
|
||||
lint:
|
||||
@if [ ! -d "$(LINT_DIR)" ]; then \
|
||||
echo "Run make lint-install"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@$(LINT_DIR)/golangci-lint run
|
||||
|
||||
# Install staticcheck
|
||||
staticcheck-install:
|
||||
@go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
@golangci-lint --timeout=5m run
|
||||
|
||||
# Run staticcheck
|
||||
staticcheck-run:
|
||||
@staticcheck ./...
|
||||
staticcheck:
|
||||
@staticcheck ./...
|
22
README.md
22
README.md
|
@ -1,22 +0,0 @@
|
|||
# Helper Go libraries for working with metrics, traces and logging
|
||||
|
||||
See package documentation
|
||||
at [pkg.go.dev](https://pkg.go.dev/git.frostfs.info/TrueCloudLab/frostfs-observability)
|
||||
|
||||
## License and copyright
|
||||
|
||||
Copyright 2023-2024 FrostFS contributors
|
||||
|
||||
```
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
```
|
46
go.mod
46
go.mod
|
@ -1,47 +1,43 @@
|
|||
module git.frostfs.info/TrueCloudLab/frostfs-observability
|
||||
|
||||
go 1.21
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.0-rc.0
|
||||
github.com/prometheus/client_golang v1.15.1
|
||||
github.com/prometheus/client_model v0.3.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
go.opentelemetry.io/otel v1.28.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0
|
||||
go.opentelemetry.io/otel/sdk v1.28.0
|
||||
go.opentelemetry.io/otel/trace v1.28.0
|
||||
google.golang.org/grpc v1.64.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
github.com/stretchr/testify v1.8.3
|
||||
go.opentelemetry.io/otel v1.16.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.16.0
|
||||
go.opentelemetry.io/otel/sdk v1.16.0
|
||||
go.opentelemetry.io/otel/trace v1.16.0
|
||||
google.golang.org/grpc v1.55.0
|
||||
google.golang.org/protobuf v1.30.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.3 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
go.uber.org/zap v1.26.0
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.16.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
129
go.sum
129
go.sum
|
@ -33,11 +33,15 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
|||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
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/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
|
@ -46,23 +50,34 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
|||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
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/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
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=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
@ -86,10 +101,11 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
|||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
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/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
|
@ -100,8 +116,8 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
|
@ -113,16 +129,15 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
|
|||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.0-rc.0 h1:mdLirNAJBxnGgyB6pjZLcs6ue/6eZGBui6gXspfq4ks=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.0-rc.0/go.mod h1:kdXbOySqcQeTxiqglW7aahTmWZy3Pgi6SYL36yvKeyA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.3 h1:o95KDiV/b1xdkumY5YbLR0/n2+wBxUpgf3HgfKgTyLI=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.3/go.mod h1:hTxjzRcX49ogbTGVJ1sM5mz5s+SSgiGIyL3jjPxl32E=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
|
@ -131,11 +146,9 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
|
|||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
@ -151,15 +164,17 @@ github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI
|
|||
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
||||
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
|
||||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -168,28 +183,26 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
|||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 h1:EVSnY9JbEEW92bEkIYOVMw4q1WJxIAGoFTrtYOzWuRQ=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0/go.mod h1:Ea1N1QQryNXpCD0I1fdLibBAIpQuBkznMmkdKrapk1Y=
|
||||
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
||||
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
||||
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
|
||||
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
|
||||
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
|
||||
go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 h1:t4ZwRPU+emrcvM2e9DHd0Fsf0JTPVcbfa/BhTDF03d0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0/go.mod h1:vLarbg68dH2Wa77g71zmKQqlQ8+8Rq3GRG31uc0WcWI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 h1:cbsD4cUcviQGXdw8+bo5x2wazq10SKz8hEbtCRPcU78=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0/go.mod h1:JgXSGah17croqhJfhByOLVY719k1emAXC8MVhCIJlRs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 h1:TVQp/bboR4mhZSav+MdgXB8FaRho1RC8UwVn3T0vjVc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0/go.mod h1:I33vtIe0sR96wfrUcilIzLoA3mLHhRmz9S9Te0S3gDo=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.16.0 h1:+XWJd3jf75RXJq29mxbuXhCXFDG3S3R4vBUeSI2P7tE=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.16.0/go.mod h1:hqgzBPTf4yONMFgdZvL/bK42R/iinTyVQtiWihs3SZc=
|
||||
go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
|
||||
go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
|
||||
go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
|
||||
go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
|
||||
go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
|
||||
go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw=
|
||||
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
@ -251,15 +264,17 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
|
|||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -294,18 +309,21 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -398,6 +416,7 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
|
|||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
|
@ -405,6 +424,7 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
|
|||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
|
@ -419,9 +439,13 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
|
|||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||
google.golang.org/grpc/examples v0.0.0-20210424002626-9572fd6faeae/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
@ -435,14 +459,15 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
|||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
package lokicore
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-observability/logging/lokicore/loki"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// Zap Core for loki.
|
||||
// Expands the zapcore.Core interface with calls to export logs to Loki.
|
||||
type LokiCore struct {
|
||||
original zapcore.Core
|
||||
encoder zapcore.Encoder
|
||||
loki *loki.Client
|
||||
}
|
||||
|
||||
func New(original zapcore.Core, lokiCfg loki.Config) *LokiCore {
|
||||
encoderConfig := zap.NewProductionEncoderConfig()
|
||||
encoder := zapcore.NewJSONEncoder(encoderConfig)
|
||||
encoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout(time.RFC3339Nano)
|
||||
|
||||
return &LokiCore{
|
||||
original: original,
|
||||
encoder: encoder,
|
||||
loki: loki.Setup(lokiCfg),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LokiCore) With(fields []zapcore.Field) zapcore.Core {
|
||||
return &LokiCore{
|
||||
original: c.original.With(fields),
|
||||
encoder: c.encoder,
|
||||
loki: c.loki,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LokiCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
||||
if err := c.original.Write(entry, fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buffer, err := c.encoder.EncodeEntry(entry, fields)
|
||||
defer buffer.Free()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.loki.Send(buffer.String(), entry.Time)
|
||||
}
|
||||
|
||||
func (c *LokiCore) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||
if c.Enabled(entry.Level) {
|
||||
return checked.AddCore(entry, c)
|
||||
}
|
||||
return checked
|
||||
}
|
||||
|
||||
func (c *LokiCore) Sync() error {
|
||||
return c.original.Sync()
|
||||
}
|
||||
|
||||
func (c *LokiCore) Enabled(level zapcore.Level) bool {
|
||||
return c.original.Enabled(level)
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
# git.frostfs.info/TrueCloudLab/frostfs-observability/loki"
|
||||
|
||||
A simple asynchronous client in Go for sending logs to Loki.
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-observability/logging/lokicore/loki"
|
||||
)
|
||||
|
||||
func main() {
|
||||
loki := loki.Setup(loki.Config{
|
||||
Address: "localhost:3100/api/prom/push",
|
||||
Labels: map[string]string{
|
||||
"label": "test",
|
||||
},
|
||||
BatchWait: 1000,
|
||||
BatchEntriesNumber: 200,
|
||||
Enabled: true,
|
||||
})
|
||||
defer loki.Shutdown()
|
||||
|
||||
loki.Send("log message", time.Now())
|
||||
}
|
||||
|
||||
```
|
|
@ -1,50 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-observability/logging/lokicore/loki"
|
||||
)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
const (
|
||||
countMsgGroup = 100
|
||||
countMsg = 500000
|
||||
)
|
||||
|
||||
func send(loki *loki.Client) {
|
||||
wg.Add(1)
|
||||
defer wg.Done()
|
||||
|
||||
for j := 0; j < countMsg/countMsgGroup; j++ {
|
||||
for i := 0; i < countMsgGroup; i++ {
|
||||
err := loki.Send(strconv.Itoa(j)+" "+strconv.Itoa(i)+" test log message", time.Now())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "send: %v", err)
|
||||
}
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
loki := loki.Setup(loki.Config{
|
||||
Endpoint: "localhost:3100/api/prom/push",
|
||||
Labels: map[string]string{
|
||||
"label": "test",
|
||||
},
|
||||
BatchWait: 1000,
|
||||
BatchEntriesNumber: 200,
|
||||
Enabled: true,
|
||||
})
|
||||
go send(loki)
|
||||
send(loki)
|
||||
|
||||
wg.Wait()
|
||||
loki.Shutdown()
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
package loki
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Send sends the message to the loki server.
|
||||
// If the client is disabled, it returns error.
|
||||
// If the entries channel is full, the message is discarded and returns error.
|
||||
func (client *Client) Send(msg string, timestamp time.Time) error {
|
||||
if !client.IsEnabled() {
|
||||
client.missedMessages++
|
||||
return errors.New("client disabled")
|
||||
}
|
||||
|
||||
if client.addToEntries(timestamp, msg) {
|
||||
if client.missedMessages > 0 {
|
||||
client.addToEntries(time.Now(), strconv.FormatInt(client.missedMessages, 10)+" messages missed")
|
||||
client.missedMessages = 0
|
||||
}
|
||||
} else {
|
||||
client.missedMessages++
|
||||
return errors.New("channel is full")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addToEntries attempts to add a log entry to the entries channel.
|
||||
// It returns true if the entry was added successfully, and false otherwise.
|
||||
func (client *Client) addToEntries(timestamp time.Time, msg string) bool {
|
||||
select {
|
||||
case client.entries <- logEntry{
|
||||
Ts: timestamp,
|
||||
Line: msg,
|
||||
}:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// run manages the sending of log batches to Loki.
|
||||
// It collects log entries into batches and sends them either when a batch is full,
|
||||
// or when the maximum wait time has elapsed.
|
||||
func (client *Client) run() {
|
||||
if !client.IsEnabled() {
|
||||
client.drainEntries()
|
||||
return
|
||||
}
|
||||
|
||||
client.waitGroup.Add(1)
|
||||
defer client.waitGroup.Done()
|
||||
|
||||
var batch []logEntry
|
||||
maxWait := time.NewTimer(client.config.BatchWait)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-client.quit:
|
||||
for len(client.entries) > 0 {
|
||||
entry := <-client.entries
|
||||
batch = append(batch, entry)
|
||||
}
|
||||
batch = client.processMissedMessages(batch)
|
||||
client.processBatch(batch)
|
||||
return
|
||||
|
||||
case entry := <-client.entries:
|
||||
batch = append(batch, entry)
|
||||
if len(batch) >= client.config.BatchEntriesNumber {
|
||||
batch = client.processBatch(batch)
|
||||
maxWait.Reset(client.config.BatchWait)
|
||||
}
|
||||
|
||||
case <-maxWait.C:
|
||||
batch = client.processBatch(batch)
|
||||
maxWait.Reset(client.config.BatchWait)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) drainEntries() {
|
||||
for len(client.entries) > 0 {
|
||||
<-client.entries
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) processBatch(batch []logEntry) []logEntry {
|
||||
if len(batch) > 0 {
|
||||
client.sendLogs(batch)
|
||||
batch = batch[:0]
|
||||
}
|
||||
return batch
|
||||
}
|
||||
|
||||
func (client *Client) processMissedMessages(batch []logEntry) []logEntry {
|
||||
if client.missedMessages > 0 {
|
||||
batch = append(batch, logEntry{
|
||||
Ts: time.Now(),
|
||||
Line: strconv.FormatInt(client.missedMessages, 10) + " messages missed",
|
||||
})
|
||||
client.missedMessages = 0
|
||||
}
|
||||
return batch
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package loki
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (client *Client) sendLogs(entries []logEntry) {
|
||||
if len(entries) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var streams []stream
|
||||
stream := stream{
|
||||
Labels: client.config.Labels,
|
||||
Entries: entries,
|
||||
}
|
||||
streams = append(streams, stream)
|
||||
|
||||
msg := promtailMsg{Streams: streams}
|
||||
jsonMsg, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
client.mutex.RLock()
|
||||
endpoint := client.config.Endpoint
|
||||
client.mutex.RUnlock()
|
||||
|
||||
client.sendRequest("POST", endpoint, "application/json", jsonMsg)
|
||||
}
|
||||
|
||||
func (client *Client) sendRequest(method, url string, ctype string, reqBody []byte) {
|
||||
req, err := http.NewRequest(method, url, bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", ctype)
|
||||
|
||||
resp, _ := client.client.Do(req)
|
||||
if resp != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *stream) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(&struct {
|
||||
Labels string `json:"labels"`
|
||||
Entries []logEntry `json:"entries"`
|
||||
}{
|
||||
Labels: p.Labels.String(),
|
||||
Entries: p.Entries,
|
||||
})
|
||||
}
|
||||
|
||||
func (l labels) String() string {
|
||||
var labelPairs []string
|
||||
for key, value := range l {
|
||||
labelPairs = append(labelPairs, fmt.Sprintf(`%s="%s"`, key, value))
|
||||
}
|
||||
return fmt.Sprintf("{%s}", strings.Join(labelPairs, ","))
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
package loki
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const logEntriesChanSize = 5000
|
||||
|
||||
// Represents a single log entry.
|
||||
type logEntry struct {
|
||||
Ts time.Time `json:"ts"`
|
||||
Line string `json:"line"`
|
||||
}
|
||||
|
||||
type labels map[string]string
|
||||
|
||||
// Stream represents a stream of log entries with associated labels.
|
||||
type stream struct {
|
||||
Labels labels `json:"-"`
|
||||
Entries []logEntry `json:"entries"`
|
||||
}
|
||||
|
||||
type promtailMsg struct {
|
||||
Streams []stream `json:"streams"`
|
||||
}
|
||||
|
||||
// Client is a client for sending log entries.
|
||||
type Client struct {
|
||||
config Config
|
||||
quit chan struct{}
|
||||
entries chan logEntry
|
||||
waitGroup sync.WaitGroup
|
||||
client http.Client
|
||||
missedMessages int64
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Enabled bool
|
||||
// E.g. localhost:3100/api/prom/push.
|
||||
Endpoint string
|
||||
Labels map[string]string
|
||||
// Maximum message buffering time.
|
||||
BatchWait time.Duration
|
||||
// Maximum number of messages in the queue.
|
||||
BatchEntriesNumber int
|
||||
}
|
||||
|
||||
// Setup initializes the client with the given configuration and starts the processing goroutine.
|
||||
// It is the caller's responsibility to call Shutdown() to free resources.
|
||||
func Setup(conf Config) *Client {
|
||||
client := newClient()
|
||||
client.config = conf
|
||||
client.config.Endpoint = normalizeURL(client.config.Endpoint)
|
||||
go client.run()
|
||||
return client
|
||||
}
|
||||
|
||||
// Shutdown stops the client and waits for all logs to be sent.
|
||||
func (client *Client) Shutdown() {
|
||||
client.mutex.Lock()
|
||||
client.config.Enabled = false
|
||||
client.mutex.Unlock()
|
||||
|
||||
close(client.quit)
|
||||
client.waitGroup.Wait()
|
||||
}
|
||||
|
||||
// IsEnabled checks whether the client is enabled.
|
||||
func (client *Client) IsEnabled() bool {
|
||||
client.mutex.RLock()
|
||||
defer client.mutex.RUnlock()
|
||||
return client.config.Enabled
|
||||
}
|
||||
|
||||
func newClient() *Client {
|
||||
return &Client{
|
||||
quit: make(chan struct{}),
|
||||
entries: make(chan logEntry, logEntriesChanSize),
|
||||
client: http.Client{},
|
||||
config: Config{Enabled: false},
|
||||
mutex: sync.RWMutex{},
|
||||
waitGroup: sync.WaitGroup{},
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeURL(host string) string {
|
||||
if !regexp.MustCompile(`^https?:\/\/`).MatchString(host) {
|
||||
host = "http://" + host
|
||||
}
|
||||
|
||||
return host
|
||||
}
|
|
@ -21,7 +21,7 @@ type Description struct {
|
|||
// NewGauge returns new registered prometheus.Gauge.
|
||||
func NewGauge(opts prometheus.GaugeOpts) prometheus.Gauge {
|
||||
value := prometheus.NewGauge(opts)
|
||||
MustRegister(value, Description{
|
||||
mustRegister(value, Description{
|
||||
Name: prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||
Type: dto.MetricType_GAUGE.String(),
|
||||
Help: opts.Help,
|
||||
|
@ -33,7 +33,7 @@ func NewGauge(opts prometheus.GaugeOpts) prometheus.Gauge {
|
|||
// NewGaugeVec returns new registered *prometheus.GaugeVec.
|
||||
func NewGaugeVec(opts prometheus.GaugeOpts, labelNames []string) *prometheus.GaugeVec {
|
||||
value := prometheus.NewGaugeVec(opts, labelNames)
|
||||
MustRegister(value, Description{
|
||||
mustRegister(value, Description{
|
||||
Name: prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||
Type: dto.MetricType_GAUGE.String(),
|
||||
Help: opts.Help,
|
||||
|
@ -47,7 +47,7 @@ func NewGaugeVec(opts prometheus.GaugeOpts, labelNames []string) *prometheus.Gau
|
|||
// NewGaugeFunc returns new registered prometheus.GaugeFunc.
|
||||
func NewGaugeFunc(opts prometheus.GaugeOpts, f func() float64) prometheus.GaugeFunc {
|
||||
value := prometheus.NewGaugeFunc(opts, f)
|
||||
MustRegister(value, Description{
|
||||
mustRegister(value, Description{
|
||||
Name: prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||
Type: dto.MetricType_GAUGE.String(),
|
||||
Help: opts.Help,
|
||||
|
@ -59,7 +59,7 @@ func NewGaugeFunc(opts prometheus.GaugeOpts, f func() float64) prometheus.GaugeF
|
|||
// NewCounter returns new registered prometheus.Counter.
|
||||
func NewCounter(opts prometheus.CounterOpts) prometheus.Counter {
|
||||
value := prometheus.NewCounter(opts)
|
||||
MustRegister(value, Description{
|
||||
mustRegister(value, Description{
|
||||
Name: prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||
Type: dto.MetricType_COUNTER.String(),
|
||||
Help: opts.Help,
|
||||
|
@ -71,7 +71,7 @@ func NewCounter(opts prometheus.CounterOpts) prometheus.Counter {
|
|||
// NewCounterVec returns new registered *prometheus.CounterVec.
|
||||
func NewCounterVec(opts prometheus.CounterOpts, labels []string) *prometheus.CounterVec {
|
||||
value := prometheus.NewCounterVec(opts, labels)
|
||||
MustRegister(value, Description{
|
||||
mustRegister(value, Description{
|
||||
Name: prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||
Type: dto.MetricType_COUNTER.String(),
|
||||
Help: opts.Help,
|
||||
|
@ -84,7 +84,7 @@ func NewCounterVec(opts prometheus.CounterOpts, labels []string) *prometheus.Cou
|
|||
// NewHistogramVec returns new registered *prometheus.HistogramVec.
|
||||
func NewHistogramVec(opts prometheus.HistogramOpts, labelNames []string) *prometheus.HistogramVec {
|
||||
value := prometheus.NewHistogramVec(opts, labelNames)
|
||||
MustRegister(value, Description{
|
||||
mustRegister(value, Description{
|
||||
Name: prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||
Type: dto.MetricType_HISTOGRAM.String(),
|
||||
Help: opts.Help,
|
||||
|
|
|
@ -4,11 +4,10 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-observability/metrics"
|
||||
grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var clientMetrics = grpcprom.NewClientMetrics(
|
||||
var clientMetrics *grpcprom.ClientMetrics = grpcprom.NewClientMetrics(
|
||||
grpcprom.WithClientHandlingTimeHistogram(
|
||||
grpcprom.WithHistogramBuckets(prometheus.DefBuckets),
|
||||
),
|
||||
|
@ -18,47 +17,7 @@ var clientMetrics = grpcprom.NewClientMetrics(
|
|||
)
|
||||
|
||||
func init() {
|
||||
// Description copied from repository of grpc-ecosystem
|
||||
// https://github.com/grpc-ecosystem/go-grpc-middleware/blob/71d7422112b1d7fadd4b8bf12a6f33ba6d22e98e/providers/prometheus/client_metrics.go#L31
|
||||
descs := []metrics.Description{
|
||||
{
|
||||
Name: "grpc_client_started_total",
|
||||
Type: dto.MetricType_COUNTER.String(),
|
||||
Help: "Total number of RPCs started on the client.",
|
||||
VariableLabels: []string{"grpc_type", "grpc_service", "grpc_method"},
|
||||
},
|
||||
{
|
||||
Name: "grpc_client_handled_total",
|
||||
Type: dto.MetricType_COUNTER.String(),
|
||||
Help: "Total number of RPCs completed by the client, regardless of success or failure.",
|
||||
VariableLabels: []string{"grpc_type", "grpc_service", "grpc_method", "grpc_code"},
|
||||
},
|
||||
{
|
||||
Name: "grpc_client_msg_received_total",
|
||||
Type: dto.MetricType_COUNTER.String(),
|
||||
Help: "Total number of RPC stream messages received by the client.",
|
||||
VariableLabels: []string{"grpc_type", "grpc_service", "grpc_method"},
|
||||
},
|
||||
{
|
||||
Name: "grpc_client_msg_sent_total",
|
||||
Type: dto.MetricType_COUNTER.String(),
|
||||
Help: "Total number of gRPC stream messages sent by the client.",
|
||||
VariableLabels: []string{"grpc_type", "grpc_service", "grpc_method"},
|
||||
},
|
||||
{
|
||||
Name: "grpc_client_handling_seconds",
|
||||
Type: dto.MetricType_HISTOGRAM.String(),
|
||||
Help: "Histogram of response latency (seconds) of the gRPC until it is finished by the application.",
|
||||
VariableLabels: []string{"grpc_type", "grpc_service", "grpc_method"},
|
||||
},
|
||||
{
|
||||
Name: "grpc_client_msg_recv_handling_seconds",
|
||||
Type: dto.MetricType_HISTOGRAM.String(),
|
||||
Help: "Histogram of response latency (seconds) of the gRPC single message receive.",
|
||||
VariableLabels: []string{"grpc_type", "grpc_service", "grpc_method"},
|
||||
},
|
||||
}
|
||||
metrics.MustRegister(clientMetrics, descs...)
|
||||
metrics.Register(clientMetrics)
|
||||
}
|
||||
|
||||
// NewUnaryClientInterceptor returns client interceptor to collect metrics from unary RPCs.
|
||||
|
|
|
@ -4,52 +4,17 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-observability/metrics"
|
||||
grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var serverMetrics = grpcprom.NewServerMetrics(
|
||||
var serverMetrics *grpcprom.ServerMetrics = grpcprom.NewServerMetrics(
|
||||
grpcprom.WithServerHandlingTimeHistogram(
|
||||
grpcprom.WithHistogramBuckets(prometheus.DefBuckets),
|
||||
),
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Description copied from grpc-ecosystem:
|
||||
// https://github.com/grpc-ecosystem/go-grpc-middleware/blob/71d7422112b1d7fadd4b8bf12a6f33ba6d22e98e/providers/prometheus/server_metrics.go#L26
|
||||
descs := []metrics.Description{
|
||||
{
|
||||
Name: "grpc_server_started_total",
|
||||
Type: dto.MetricType_COUNTER.String(),
|
||||
Help: "Total number of RPCs started on the server.",
|
||||
VariableLabels: []string{"grpc_type", "grpc_service", "grpc_method"},
|
||||
},
|
||||
{
|
||||
Name: "grpc_server_handled_total",
|
||||
Type: dto.MetricType_COUNTER.String(),
|
||||
Help: "Total number of RPCs completed on the server, regardless of success or failure.",
|
||||
VariableLabels: []string{"grpc_type", "grpc_service", "grpc_method", "grpc_code"},
|
||||
},
|
||||
{
|
||||
Name: "grpc_server_msg_received_total",
|
||||
Type: dto.MetricType_COUNTER.String(),
|
||||
Help: "Total number of RPC stream messages received on the server.",
|
||||
VariableLabels: []string{"grpc_type", "grpc_service", "grpc_method"},
|
||||
},
|
||||
{
|
||||
Name: "grpc_server_msg_sent_total",
|
||||
Type: dto.MetricType_COUNTER.String(),
|
||||
Help: "Total number of gRPC stream messages sent by the server.",
|
||||
VariableLabels: []string{"grpc_type", "grpc_service", "grpc_method"},
|
||||
},
|
||||
{
|
||||
Name: "grpc_server_handling_seconds",
|
||||
Type: dto.MetricType_HISTOGRAM.String(),
|
||||
Help: "Histogram of response latency (seconds) of gRPC that had been application-level handled by the server.",
|
||||
VariableLabels: []string{"grpc_type", "grpc_service", "grpc_method"},
|
||||
},
|
||||
}
|
||||
metrics.MustRegister(serverMetrics, descs...)
|
||||
metrics.Register(serverMetrics)
|
||||
}
|
||||
|
||||
// NewUnaryServerInterceptor returns server interceptor to collect metrics from unary RPCs.
|
||||
|
|
|
@ -28,11 +28,11 @@ func Register(customCollectors ...prometheus.Collector) {
|
|||
registry.MustRegister(customCollectors...)
|
||||
}
|
||||
|
||||
func MustRegister(c prometheus.Collector, descs ...Description) {
|
||||
func mustRegister(c prometheus.Collector, desc Description) {
|
||||
registry.MustRegister(c)
|
||||
registeredDescriptionsMtx.Lock()
|
||||
defer registeredDescriptionsMtx.Unlock()
|
||||
registeredDescriptions = append(registeredDescriptions, descs...)
|
||||
registeredDescriptions = append(registeredDescriptions, desc)
|
||||
}
|
||||
|
||||
// Handler returns an http.Handler for the local registry.
|
||||
|
|
0
testdata/tracing/invalid_empty_root_ca.pem
vendored
0
testdata/tracing/invalid_empty_root_ca.pem
vendored
1
testdata/tracing/invalid_root_ca.pem
vendored
1
testdata/tracing/invalid_root_ca.pem
vendored
|
@ -1 +0,0 @@
|
|||
invalid content
|
|
@ -1,12 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD
|
||||
VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh
|
||||
bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw
|
||||
MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g
|
||||
UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT
|
||||
BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx
|
||||
uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV
|
||||
HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/
|
||||
+wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147
|
||||
bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
|
||||
-----END CERTIFICATE-----
|
|
@ -1,13 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD
|
||||
VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
|
||||
A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
|
||||
WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
|
||||
IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
|
||||
AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi
|
||||
QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR
|
||||
HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
|
||||
BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D
|
||||
9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8
|
||||
p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD
|
||||
-----END CERTIFICATE-----
|
|
@ -1,10 +1,6 @@
|
|||
package tracing
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"maps"
|
||||
)
|
||||
import "fmt"
|
||||
|
||||
// Exporter is type of tracing target.
|
||||
type Exporter string
|
||||
|
@ -22,8 +18,6 @@ type Config struct {
|
|||
Exporter Exporter
|
||||
// Endpoint is collector endpoint for OTLP exporters.
|
||||
Endpoint string
|
||||
// ServerCaCertPool is cert pool of the remote server CA certificate. Use for TLS setup.
|
||||
ServerCaCertPool *x509.CertPool
|
||||
|
||||
// Service is service name that will be used in tracing.
|
||||
// Mandatory.
|
||||
|
@ -34,9 +28,6 @@ type Config struct {
|
|||
// Version is version of service instance.
|
||||
// Optional.
|
||||
Version string
|
||||
// Attributes is KV list of attributes.
|
||||
// Optional.
|
||||
Attributes map[string]string
|
||||
}
|
||||
|
||||
func (c *Config) validate() error {
|
||||
|
@ -73,10 +64,6 @@ func (c *Config) hasChange(other *Config) bool {
|
|||
return !c.serviceInfoEqual(other)
|
||||
}
|
||||
|
||||
if other.Exporter == OTLPgRPCExporter && !c.ServerCaCertPool.Equal(other.ServerCaCertPool) {
|
||||
return true
|
||||
}
|
||||
|
||||
return c.Exporter != other.Exporter ||
|
||||
c.Endpoint != other.Endpoint ||
|
||||
!c.serviceInfoEqual(other)
|
||||
|
@ -85,6 +72,5 @@ func (c *Config) hasChange(other *Config) bool {
|
|||
func (c *Config) serviceInfoEqual(other *Config) bool {
|
||||
return c.Service == other.Service &&
|
||||
c.InstanceID == other.InstanceID &&
|
||||
c.Version == other.Version &&
|
||||
maps.Equal(c.Attributes, other.Attributes)
|
||||
c.Version == other.Version
|
||||
}
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
package tracing
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConfig_validate(t *testing.T) {
|
||||
|
@ -236,28 +232,6 @@ func TestConfig_hasChange(t *testing.T) {
|
|||
Version: "v1.0.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "use tls root ca certificate for grpc",
|
||||
want: true,
|
||||
config: Config{
|
||||
Enabled: true,
|
||||
Exporter: OTLPgRPCExporter,
|
||||
Endpoint: "localhost:4717",
|
||||
Service: "test",
|
||||
InstanceID: "s01",
|
||||
Version: "v1.0.0",
|
||||
ServerCaCertPool: readCertPoolByPath(t, "../testdata/tracing/valid_google_globalsign_r4_rsa_root_ca.pem"),
|
||||
},
|
||||
other: Config{
|
||||
Enabled: true,
|
||||
Exporter: OTLPgRPCExporter,
|
||||
Endpoint: "localhost:4717",
|
||||
Service: "test",
|
||||
InstanceID: "s01",
|
||||
Version: "v1.0.0",
|
||||
ServerCaCertPool: readCertPoolByPath(t, "../testdata/tracing/valid_google_gts_r4_ecdsa_root_ca.pem"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -267,12 +241,3 @@ func TestConfig_hasChange(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func readCertPoolByPath(t *testing.T, path string) *x509.CertPool {
|
||||
ca, err := os.ReadFile(path)
|
||||
require.NoError(t, err)
|
||||
roots := x509.NewCertPool()
|
||||
ok := roots.AppendCertsFromPEM(ca)
|
||||
require.True(t, ok)
|
||||
return roots
|
||||
}
|
||||
|
|
|
@ -79,7 +79,8 @@ func main() {
|
|||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
cc, err := grpc.NewClient(":7000",
|
||||
cc, err := grpc.DialContext(ctx,
|
||||
":7000",
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithChainUnaryInterceptor(
|
||||
tracing_grpc.NewUnaryClientInteceptor(),
|
||||
|
@ -96,6 +97,7 @@ func main() {
|
|||
resp, err := client.Echo(ctx, &srv.Request{
|
||||
Value: "Hello!",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get response: %v", err)
|
||||
}
|
||||
|
|
|
@ -14,4 +14,4 @@ message Request {
|
|||
|
||||
message Response {
|
||||
string value = 1;
|
||||
}
|
||||
}
|
|
@ -70,7 +70,7 @@ func (p *propagator) Extract(ctx context.Context, carrier propagation.TextMapCar
|
|||
}
|
||||
|
||||
if traceIDDefined != spanIDDefined {
|
||||
return ctx // traceID + spanID must be defined OR no traceID and no spanID
|
||||
return ctx //traceID + spanID must be defined OR no traceID and no spanID
|
||||
}
|
||||
|
||||
flagsStr := carrier.Get(flagsHeader)
|
||||
|
|
|
@ -2,8 +2,8 @@ package tracing
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -124,6 +124,7 @@ func TestPropagator_Inject(t *testing.T) {
|
|||
require.Equal(t, spanIDHex, c.Values[spanIDHeader], "unexpected span id")
|
||||
require.Equal(t, "0", c.Values[flagsHeader], "unexpected flags")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestPropagator_Extract(t *testing.T) {
|
||||
|
|
|
@ -2,9 +2,8 @@ package tracing
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
|
@ -13,16 +12,10 @@ import (
|
|||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
// ErrEmptyServerRootCaPool indicates that cert pool is empty and does not have any certificates inside.
|
||||
var ErrEmptyServerRootCaPool = errors.New("empty server root ca cert pool")
|
||||
|
||||
var (
|
||||
// tracingLock protects provider, done, config and tracer from concurrent update.
|
||||
// These fields change when the config is updated or the application is shutdown.
|
||||
|
@ -56,7 +49,7 @@ func Setup(ctx context.Context, cfg Config) (bool, error) {
|
|||
|
||||
if !cfg.Enabled {
|
||||
config = cfg
|
||||
tracer.Store(&tracerHolder{Tracer: noop.NewTracerProvider().Tracer("")})
|
||||
tracer.Store(&tracerHolder{Tracer: trace.NewNoopTracerProvider().Tracer("")})
|
||||
return true, flushAndShutdown(ctx)
|
||||
}
|
||||
|
||||
|
@ -102,14 +95,14 @@ func Shutdown(ctx context.Context) error {
|
|||
done = true
|
||||
|
||||
config = Config{}
|
||||
tracer.Store(&tracerHolder{Tracer: noop.NewTracerProvider().Tracer("")})
|
||||
tracer.Store(&tracerHolder{Tracer: trace.NewNoopTracerProvider().Tracer("")})
|
||||
|
||||
return flushAndShutdown(ctx)
|
||||
}
|
||||
|
||||
func getDefaultTracer() *atomic.Pointer[tracerHolder] {
|
||||
v := new(atomic.Pointer[tracerHolder])
|
||||
v.Store(&tracerHolder{Tracer: noop.NewTracerProvider().Tracer("")})
|
||||
v.Store(&tracerHolder{Tracer: trace.NewNoopTracerProvider().Tracer("")})
|
||||
return v
|
||||
}
|
||||
|
||||
|
@ -139,16 +132,9 @@ func getExporter(ctx context.Context, cfg *Config) (sdktrace.SpanExporter, error
|
|||
case StdoutExporter:
|
||||
return stdouttrace.New()
|
||||
case NoOpExporter:
|
||||
return tracetest.NewNoopExporter(), nil
|
||||
return stdouttrace.New(stdouttrace.WithWriter(io.Discard))
|
||||
case OTLPgRPCExporter:
|
||||
securityOption := otlptracegrpc.WithInsecure()
|
||||
if cfg.ServerCaCertPool != nil {
|
||||
if cfg.ServerCaCertPool.Equal(x509.NewCertPool()) {
|
||||
return nil, fmt.Errorf("failed to setup tracing: %w", ErrEmptyServerRootCaPool)
|
||||
}
|
||||
securityOption = otlptracegrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(cfg.ServerCaCertPool, ""))
|
||||
}
|
||||
return otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint(cfg.Endpoint), securityOption)
|
||||
return otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint(cfg.Endpoint), otlptracegrpc.WithInsecure())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,9 +148,6 @@ func newResource(cfg *Config) *resource.Resource {
|
|||
if len(cfg.InstanceID) > 0 {
|
||||
attrs = append(attrs, semconv.ServiceInstanceID(cfg.InstanceID))
|
||||
}
|
||||
for k, v := range cfg.Attributes {
|
||||
attrs = append(attrs, attribute.String(k, v))
|
||||
}
|
||||
return resource.NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
attrs...,
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
package tracing_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config tracing.Config
|
||||
want bool
|
||||
expErr error
|
||||
}{
|
||||
{
|
||||
name: "setup stdout exporter",
|
||||
config: tracing.Config{
|
||||
Enabled: true,
|
||||
Exporter: tracing.StdoutExporter,
|
||||
Service: "service-name",
|
||||
},
|
||||
want: true,
|
||||
expErr: nil,
|
||||
},
|
||||
{
|
||||
name: "setup noop exporter",
|
||||
config: tracing.Config{
|
||||
Enabled: true,
|
||||
Exporter: tracing.NoOpExporter,
|
||||
Service: "service-name",
|
||||
},
|
||||
|
||||
want: true,
|
||||
expErr: nil,
|
||||
},
|
||||
{
|
||||
name: "setup otlp_grpc insecure exporter",
|
||||
config: tracing.Config{
|
||||
Enabled: true,
|
||||
Exporter: tracing.OTLPgRPCExporter,
|
||||
Service: "service-name",
|
||||
Endpoint: "test-endpoint.com:4317",
|
||||
},
|
||||
want: true,
|
||||
expErr: nil,
|
||||
},
|
||||
{
|
||||
name: "setup otlp_grpc secure exporter with valid rsa root ca certificate",
|
||||
config: tracing.Config{
|
||||
Enabled: true,
|
||||
Exporter: tracing.OTLPgRPCExporter,
|
||||
Service: "service-name",
|
||||
Endpoint: "test-endpoint.com:4317",
|
||||
ServerCaCertPool: readCertPoolByPath(t, "../testdata/tracing/valid_google_globalsign_r4_rsa_root_ca.pem"),
|
||||
},
|
||||
want: true,
|
||||
expErr: nil,
|
||||
},
|
||||
{
|
||||
name: "setup otlp_grpc secure exporter with valid ecdsa root ca certificate",
|
||||
config: tracing.Config{
|
||||
Enabled: true,
|
||||
Exporter: tracing.OTLPgRPCExporter,
|
||||
Service: "service-name",
|
||||
Endpoint: "test-endpoint.com:4317",
|
||||
ServerCaCertPool: readCertPoolByPath(t, "../testdata/tracing/valid_google_gts_r4_ecdsa_root_ca.pem"),
|
||||
},
|
||||
want: true,
|
||||
expErr: nil,
|
||||
},
|
||||
{
|
||||
name: "setup otlp_grpc secure exporter with invalid root ca certificate",
|
||||
config: tracing.Config{
|
||||
Enabled: true,
|
||||
Exporter: tracing.OTLPgRPCExporter,
|
||||
Service: "service-name",
|
||||
Endpoint: "test-endpoint.com:4317",
|
||||
ServerCaCertPool: readCertPoolByPath(t, "../testdata/tracing/invalid_root_ca.pem"),
|
||||
},
|
||||
want: false,
|
||||
expErr: tracing.ErrEmptyServerRootCaPool,
|
||||
},
|
||||
{
|
||||
name: "setup otlp_grpc secure exporter with empty root ca certificate",
|
||||
config: tracing.Config{
|
||||
Enabled: true,
|
||||
Exporter: tracing.OTLPgRPCExporter,
|
||||
Service: "service-name",
|
||||
Endpoint: "test-endpoint.com:4317",
|
||||
ServerCaCertPool: readCertPoolByPath(t, "../testdata/tracing/invalid_empty_root_ca.pem"),
|
||||
},
|
||||
want: false,
|
||||
expErr: tracing.ErrEmptyServerRootCaPool,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tracing.Setup(context.Background(), tt.config)
|
||||
require.ErrorIs(t, err, tt.expErr)
|
||||
if got != tt.want {
|
||||
t.Errorf("Setup config = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func readCertPoolByPath(t *testing.T, path string) *x509.CertPool {
|
||||
ca, err := os.ReadFile(path)
|
||||
require.NoError(t, err)
|
||||
roots := x509.NewCertPool()
|
||||
_ = roots.AppendCertsFromPEM(ca)
|
||||
return roots
|
||||
}
|
Loading…
Reference in a new issue