diff --git a/go.mod b/go.mod index 1d1594ca97..96f534fd47 100644 --- a/go.mod +++ b/go.mod @@ -81,6 +81,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 github.com/google/go-cmp v0.6.0 github.com/sercand/kuberesolver/v5 v5.1.1 + github.com/tjhop/slog-gokit v0.1.2 go.opentelemetry.io/collector/pdata v1.21.0 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 google.golang.org/protobuf v1.35.2 diff --git a/go.sum b/go.sum index 0aeecbfe82..363c11ab61 100644 --- a/go.sum +++ b/go.sum @@ -1667,6 +1667,8 @@ github.com/thanos-io/promql-engine v0.0.0-20241203103240-2f49f80c7c68 h1:cChM/Fb github.com/thanos-io/promql-engine v0.0.0-20241203103240-2f49f80c7c68/go.mod h1:wx0JlRZtsB2S10JYUgeg5GqLfMxw31SzArP+28yyE00= github.com/thanos-io/thanos v0.37.1 h1:PuTMql3S/i5UWlBT0WbCDwwL6Kc6Jf7DLHt2rdj4ivY= github.com/thanos-io/thanos v0.37.1/go.mod h1:5Ni7Uc1Bc8UCGOYmZ/2f/LVvDkZKNDdqDJZqjDFG+rI= +github.com/tjhop/slog-gokit v0.1.2 h1:pmQI4SvU9h4gA0vIQsdhJQSqQg4mOmsPykG2/PM3j1I= +github.com/tjhop/slog-gokit v0.1.2/go.mod h1:8fhlcp8C8ELbg3GCyKv06tgt4B5sDq2P1r2DQAu1HuM= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= diff --git a/pkg/querier/tripperware/queryrange/query_range_middlewares_test.go b/pkg/querier/tripperware/queryrange/query_range_middlewares_test.go index 5acb18d87e..c8238a9a62 100644 --- a/pkg/querier/tripperware/queryrange/query_range_middlewares_test.go +++ b/pkg/querier/tripperware/queryrange/query_range_middlewares_test.go @@ -25,7 +25,6 @@ var ( ) func TestRoundTrip(t *testing.T) { - t.Parallel() s := httptest.NewServer( middleware.AuthenticateUser.Wrap( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/querier/tripperware/queryrange/split_by_interval_test.go b/pkg/querier/tripperware/queryrange/split_by_interval_test.go index 4d7e4a9a68..e9cfd8526f 100644 --- a/pkg/querier/tripperware/queryrange/split_by_interval_test.go +++ b/pkg/querier/tripperware/queryrange/split_by_interval_test.go @@ -330,7 +330,6 @@ func TestSplitByDay(t *testing.T) { } func Test_evaluateAtModifier(t *testing.T) { - t.Parallel() const ( start, end = int64(1546300800), int64(1646300800) ) @@ -386,7 +385,6 @@ func Test_evaluateAtModifier(t *testing.T) { } { tt := tt t.Run(tt.in, func(t *testing.T) { - t.Parallel() out, err := evaluateAtModifierFunction(tt.in, start, end) if tt.expectedErrorCode != 0 { require.Error(t, err) diff --git a/pkg/util/log/log.go b/pkg/util/log/log.go index 52b80b71f2..b93c94a13f 100644 --- a/pkg/util/log/log.go +++ b/pkg/util/log/log.go @@ -62,7 +62,8 @@ func InitLogger(cfg *server.Config) { // PrometheusLogger exposes Prometheus counters for each of go-kit's log levels. type PrometheusLogger struct { - logger log.Logger + logger log.Logger + logLevel logging.Level } // NewPrometheusLogger creates a new instance of PrometheusLogger which exposes @@ -92,7 +93,8 @@ func newPrometheusLoggerFrom(logger log.Logger, logLevel logging.Level, keyvals logMessages.WithLabelValues(level.String()) } return &PrometheusLogger{ - logger: logger, + logger: logger, + logLevel: logLevel, } } diff --git a/pkg/util/log/slog_adapter.go b/pkg/util/log/slog_adapter.go new file mode 100644 index 0000000000..eec19bb4a5 --- /dev/null +++ b/pkg/util/log/slog_adapter.go @@ -0,0 +1,30 @@ +package log + +import ( + "log/slog" + + "github.com/go-kit/log" + sloggk "github.com/tjhop/slog-gokit" +) + +// GoKitLogToSlog convert go-kit/log to slog +// usage: logutil.GoKitLogToSlog(gokitLogger) +func GoKitLogToSlog(logger log.Logger) *slog.Logger { + levelVar := slog.LevelVar{} + promLogger, ok := logger.(*PrometheusLogger) + if !ok { + levelVar.Set(slog.LevelDebug) + } else { + switch promLogger.logLevel.String() { + case "debug": + levelVar.Set(slog.LevelDebug) + case "info": + levelVar.Set(slog.LevelInfo) + case "warn": + levelVar.Set(slog.LevelWarn) + case "error": + levelVar.Set(slog.LevelError) + } + } + return slog.New(sloggk.NewGoKitHandler(logger, &levelVar)) +} diff --git a/pkg/util/log/slog_adapter_test.go b/pkg/util/log/slog_adapter_test.go new file mode 100644 index 0000000000..d37076d0dd --- /dev/null +++ b/pkg/util/log/slog_adapter_test.go @@ -0,0 +1,33 @@ +package log + +import ( + "context" + "log/slog" + "testing" + + "github.com/stretchr/testify/require" + "github.com/weaveworks/common/server" +) + +func Test_SlogAdapter_LogLevel(t *testing.T) { + ctx := context.Background() + logLevels := []string{"debug", "info", "warn", "error"} + slogLevels := []slog.Level{slog.LevelDebug, slog.LevelInfo, slog.LevelWarn, slog.LevelError} + + for i, lv := range logLevels { + cfg := &server.Config{} + require.NoError(t, cfg.LogLevel.Set(lv)) + InitLogger(cfg) + + slog := GoKitLogToSlog(Logger) + for j, slogLv := range slogLevels { + if i <= j { + t.Logf("[go-kit log level: %v, slog level: %v] slog should be enabled", lv, slogLv) + require.True(t, slog.Enabled(ctx, slogLv)) + } else { + t.Logf("[go-kit log level: %v, slog level: %v] slog should be disabled", lv, slogLv) + require.False(t, slog.Enabled(ctx, slogLv)) + } + } + } +} diff --git a/vendor/github.com/tjhop/slog-gokit/.goreleaser.yaml b/vendor/github.com/tjhop/slog-gokit/.goreleaser.yaml new file mode 100644 index 0000000000..f092384c21 --- /dev/null +++ b/vendor/github.com/tjhop/slog-gokit/.goreleaser.yaml @@ -0,0 +1,29 @@ +version: 2 + +builds: +- skip: true +gomod: + proxy: true + mod: mod +checksum: + name_template: 'checksums.txt' +snapshot: + version_template: "{{ incpatch .Version }}-next" +changelog: + sort: asc + filters: + exclude: + - '^Merge pull request' + - '^ci(?:\(\w+\))?\!?:' + - '^docs(?:\(\w+\))?\!?:' + - '^test(?:\(\w+\))?\!?:' + - '^style(?:\(\w+\))?\!?:' + groups: + - title: "New Features And Changes" + regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$' + order: 0 + - title: "Fixes" + regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$' + order: 1 + - title: "Other Changes" + order: 999 diff --git a/vendor/github.com/tjhop/slog-gokit/LICENSE b/vendor/github.com/tjhop/slog-gokit/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/vendor/github.com/tjhop/slog-gokit/LICENSE @@ -0,0 +1,201 @@ + 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. diff --git a/vendor/github.com/tjhop/slog-gokit/Makefile b/vendor/github.com/tjhop/slog-gokit/Makefile new file mode 100644 index 0000000000..dce8bb04e1 --- /dev/null +++ b/vendor/github.com/tjhop/slog-gokit/Makefile @@ -0,0 +1,27 @@ +GOCMD := go +GOFMT := ${GOCMD} fmt +GOMOD := ${GOCMD} mod +GOTEST := ${GOCMD} test +GOLANGCILINT_CACHE := ${CURDIR}/.golangci-lint/build/cache + +# autogenerate help messages for comment lines with 2 `#` +.PHONY: help +help: ## print this help message + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n\nTargets:\n"} /^[a-z0-9A-Z_-]+:.*?##/ { printf " \033[36m%-30s\033[0m%s\n", $$1, $$2 }' $(MAKEFILE_LIST) + +.PHONY: tidy +tidy: ## tidy modules + ${GOMOD} tidy + +.PHONY: fmt +fmt: ## apply go code style formatter + ${GOFMT} -x ./... + +.PHONY: lint +lint: ## run linters + mkdir -p ${GOLANGCILINT_CACHE} || true + docker run --rm -v ${CURDIR}:/app -v ${GOLANGCILINT_CACHE}:/root/.cache -w /app docker.io/golangci/golangci-lint:latest golangci-lint run -v + +.PHONY: test +test: ## run go tests + ${GOTEST} -race -v . diff --git a/vendor/github.com/tjhop/slog-gokit/README.md b/vendor/github.com/tjhop/slog-gokit/README.md new file mode 100644 index 0000000000..d6b5e51de6 --- /dev/null +++ b/vendor/github.com/tjhop/slog-gokit/README.md @@ -0,0 +1,40 @@ +# Go slog-gokit Adapter + +This library provides a custom slog.Handler that wraps a go-kit Logger, so that loggers created via `slog.New()` chain their log calls to the internal go-kit Logger. + +## Install + +```bash +go get github.com/tjhop/slog-gokit +``` + +## Example + +```go +package main + +import ( + "log/slog" + "os" + + "github.com/go-kit/log" + slgk "github.com/tjhop/slog-gokit" +) + +func main() { + // Take an existing go-kit/log Logger: + gklogger := log.NewLogfmtLogger(os.Stderr) + + // Create an slog Logger that chains log calls to the go-kit/log Logger: + slogger := slog.New(slgk.NewGoKitHandler(gklogger, nil)) + slogger.WithGroup("example_group").With("foo", "bar").Info("hello world") + + // The slog Logger produced logs at slog.LevelInfo by default. + // Optionally create an slog.Leveler to dynamically adjust the level of + // the slog Logger. + lvl := &slog.LevelVar{} + lvl.Set(slog.LevelDebug) + slogger = slog.New(slgk.NewGoKitHandler(gklogger, lvl)) + slogger.WithGroup("example_group").With("foo", "bar").Info("hello world") +} +``` diff --git a/vendor/github.com/tjhop/slog-gokit/handler.go b/vendor/github.com/tjhop/slog-gokit/handler.go new file mode 100644 index 0000000000..a926595292 --- /dev/null +++ b/vendor/github.com/tjhop/slog-gokit/handler.go @@ -0,0 +1,139 @@ +package sloggokit + +import ( + "context" + "log/slog" + "os" + + "github.com/go-kit/log" +) + +var _ slog.Handler = (*GoKitHandler)(nil) + +var defaultGoKitLogger = log.NewLogfmtLogger(os.Stderr) + +// GoKitHandler implements the slog.Handler interface. It holds an internal +// go-kit logger that is used to perform the true logging. +type GoKitHandler struct { + level slog.Leveler + logger log.Logger + preformatted []any + group string +} + +// NewGoKitHandler returns a new slog logger from the provided go-kit +// logger. Calls to the slog logger are chained to the handler's internal +// go-kit logger. If provided a level, it will be used to filter log events in +// the handler's Enabled() method. +func NewGoKitHandler(logger log.Logger, level slog.Leveler) slog.Handler { + if logger == nil { + logger = defaultGoKitLogger + } + + // Adjust runtime call depth to compensate for the adapter and point to + // the appropriate source line. + logger = log.With(logger, "caller", log.Caller(6)) + + if level == nil { + level = &slog.LevelVar{} // Info level by default. + } + + return &GoKitHandler{logger: logger, level: level} +} + +func (h *GoKitHandler) Enabled(_ context.Context, level slog.Level) bool { + if h.level == nil { + h.level = &slog.LevelVar{} // Info level by default. + } + + return level >= h.level.Level() +} + +func (h *GoKitHandler) Handle(_ context.Context, record slog.Record) error { + if h.logger == nil { + h.logger = defaultGoKitLogger + } + + logger := goKitLevelFunc(h.logger, record.Level) + + // 1 slog.Attr == 1 key and 1 value, set capacity >= (2 * num attrs). + // + // Note: this could probably be (micro)-optimized further -- we know we + // need to also append on a timestamp from the record, the message, the + // preformatted vals, all things we more or less know the size of at + // creation time here. + pairs := make([]any, 0, (2 * record.NumAttrs())) + if !record.Time.IsZero() { + pairs = append(pairs, "time", record.Time) + } + pairs = append(pairs, "msg", record.Message) + pairs = append(pairs, h.preformatted...) + + record.Attrs(func(a slog.Attr) bool { + pairs = appendPair(pairs, h.group, a) + return true + }) + + return logger.Log(pairs...) +} + +func (h *GoKitHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + pairs := make([]any, 0, 2*len(attrs)) + for _, a := range attrs { + pairs = appendPair(pairs, h.group, a) + } + + if h.preformatted != nil { + pairs = append(h.preformatted, pairs...) + } + + return &GoKitHandler{ + logger: h.logger, + level: h.level, + preformatted: pairs, + group: h.group, + } +} + +func (h *GoKitHandler) WithGroup(name string) slog.Handler { + if name == "" { + return h + } + + g := name + if h.group != "" { + g = h.group + "." + g + } + + return &GoKitHandler{ + logger: h.logger, + level: h.level, + preformatted: h.preformatted, + group: g, + } +} + +func appendPair(pairs []any, groupPrefix string, attr slog.Attr) []any { + if attr.Equal(slog.Attr{}) { + return pairs + } + + switch attr.Value.Kind() { + case slog.KindGroup: + if attr.Key != "" { + groupPrefix = groupPrefix + "." + attr.Key + } + for _, a := range attr.Value.Group() { + pairs = appendPair(pairs, groupPrefix, a) + } + default: + key := attr.Key + if groupPrefix != "" { + key = groupPrefix + "." + key + } + + pairs = append(pairs, key, attr.Value) + } + + return pairs +} diff --git a/vendor/github.com/tjhop/slog-gokit/level.go b/vendor/github.com/tjhop/slog-gokit/level.go new file mode 100644 index 0000000000..467d991a9c --- /dev/null +++ b/vendor/github.com/tjhop/slog-gokit/level.go @@ -0,0 +1,23 @@ +package sloggokit + +import ( + "log/slog" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" +) + +func goKitLevelFunc(logger log.Logger, lvl slog.Level) log.Logger { + switch lvl { + case slog.LevelInfo: + logger = level.Info(logger) + case slog.LevelWarn: + logger = level.Warn(logger) + case slog.LevelError: + logger = level.Error(logger) + default: + logger = level.Debug(logger) + } + + return logger +} diff --git a/vendor/modules.txt b/vendor/modules.txt index c92285c6a8..4b2a4f3e91 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1045,6 +1045,9 @@ github.com/thanos-io/thanos/pkg/tracing/interceptors github.com/thanos-io/thanos/pkg/tracing/migration github.com/thanos-io/thanos/pkg/tracing/tracing_middleware github.com/thanos-io/thanos/pkg/tracing/util/metautils +# github.com/tjhop/slog-gokit v0.1.2 +## explicit; go 1.21 +github.com/tjhop/slog-gokit # github.com/uber/jaeger-client-go v2.30.0+incompatible ## explicit github.com/uber/jaeger-client-go