Skip to content

Commit bdd43c1

Browse files
committed
feat: add opentelemetry tracing
1 parent fb8f8fd commit bdd43c1

15 files changed

+330
-67
lines changed

cmd/cosmos-wallets-exporter.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ var (
1313
)
1414

1515
func ExecuteMain(configPath string) {
16-
app := pkg.NewApp(configPath)
16+
app := pkg.NewApp(configPath, version)
1717
app.Start()
1818
}
1919

go.mod

+27-7
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,47 @@
11
module main
22

3-
go 1.18
3+
go 1.21.4
4+
5+
toolchain go1.22.1
46

57
require (
68
github.com/BurntSushi/toml v1.1.0
7-
github.com/google/uuid v1.3.0
9+
github.com/google/uuid v1.6.0
10+
github.com/guregu/null/v5 v5.0.0
811
github.com/mcuadros/go-defaults v1.2.0
912
github.com/prometheus/client_golang v1.12.2
1013
github.com/rs/zerolog v1.26.1
1114
github.com/spf13/cobra v1.4.0
12-
github.com/superoo7/go-gecko v1.0.0
15+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0
16+
go.opentelemetry.io/otel v1.26.0
17+
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0
18+
go.opentelemetry.io/otel/sdk v1.26.0
19+
go.opentelemetry.io/otel/trace v1.26.0
1320
)
1421

1522
require (
1623
github.com/beorn7/perks v1.0.1 // indirect
17-
github.com/cespare/xxhash/v2 v2.1.2 // indirect
18-
github.com/golang/protobuf v1.5.2 // indirect
24+
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
25+
github.com/cespare/xxhash/v2 v2.2.0 // indirect
26+
github.com/felixge/httpsnoop v1.0.4 // indirect
27+
github.com/go-logr/logr v1.4.1 // indirect
28+
github.com/go-logr/stdr v1.2.2 // indirect
29+
github.com/golang/protobuf v1.5.4 // indirect
30+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
1931
github.com/inconshreveable/mousetrap v1.0.0 // indirect
2032
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
2133
github.com/prometheus/client_model v0.2.0 // indirect
2234
github.com/prometheus/common v0.32.1 // indirect
2335
github.com/prometheus/procfs v0.7.3 // indirect
2436
github.com/spf13/pflag v1.0.5 // indirect
25-
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
26-
google.golang.org/protobuf v1.26.0 // indirect
37+
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect
38+
go.opentelemetry.io/otel/metric v1.26.0 // indirect
39+
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
40+
golang.org/x/net v0.23.0 // indirect
41+
golang.org/x/sys v0.19.0 // indirect
42+
golang.org/x/text v0.14.0 // indirect
43+
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
44+
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
45+
google.golang.org/grpc v1.63.2 // indirect
46+
google.golang.org/protobuf v1.33.0 // indirect
2747
)

go.sum

+67-21
Large diffs are not rendered by default.

pkg/app.go

+33-14
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
11
package pkg
22

33
import (
4+
"context"
45
coingeckoPkg "main/pkg/coingecko"
56
"main/pkg/config"
67
"main/pkg/logger"
78
queriersPkg "main/pkg/queriers"
9+
"main/pkg/tracing"
810
"main/pkg/types"
911
"net/http"
1012
"sync"
1113
"time"
1214

15+
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
16+
"go.opentelemetry.io/otel/attribute"
17+
1318
"github.com/google/uuid"
1419
"github.com/prometheus/client_golang/prometheus"
1520
"github.com/prometheus/client_golang/prometheus/promhttp"
1621
"github.com/rs/zerolog"
22+
"go.opentelemetry.io/otel/trace"
1723
)
1824

1925
type App struct {
2026
Config *config.Config
2127
Logger zerolog.Logger
2228
Queriers []types.Querier
29+
30+
Tracer trace.Tracer
2331
}
2432

25-
func NewApp(configPath string) *App {
33+
func NewApp(configPath string, version string) *App {
2634
appConfig, err := config.GetConfig(configPath)
2735
if err != nil {
2836
logger.GetDefaultLogger().Fatal().Err(err).Msg("Could not load config")
@@ -32,26 +40,31 @@ func NewApp(configPath string) *App {
3240
logger.GetDefaultLogger().Fatal().Err(err).Msg("Provided config is invalid!")
3341
}
3442

43+
tracer, err := tracing.InitTracer(appConfig.TracingConfig, version)
44+
if err != nil {
45+
logger.GetDefaultLogger().Fatal().Err(err).Msg("Error setting up tracing")
46+
}
47+
3548
log := logger.GetLogger(appConfig.LogConfig)
36-
coingecko := coingeckoPkg.NewCoingecko(appConfig, log)
49+
coingecko := coingeckoPkg.NewCoingecko(appConfig, log, tracer)
3750

3851
queriers := []types.Querier{
39-
queriersPkg.NewPriceQuerier(appConfig, coingecko),
40-
queriersPkg.NewBalanceQuerier(appConfig, log),
41-
queriersPkg.NewUptimeQuerier(),
52+
queriersPkg.NewPriceQuerier(appConfig, coingecko, tracer),
53+
queriersPkg.NewBalanceQuerier(appConfig, log, tracer),
54+
queriersPkg.NewUptimeQuerier(tracer),
4255
}
4356

4457
return &App{
4558
Config: appConfig,
4659
Logger: log,
4760
Queriers: queriers,
61+
Tracer: tracer,
4862
}
4963
}
5064

5165
func (a *App) Start() {
52-
http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
53-
a.Handler(w, r)
54-
})
66+
otelHandler := otelhttp.NewHandler(http.HandlerFunc(a.Handler), "prometheus")
67+
http.Handle("/metrics", otelHandler)
5568

5669
a.Logger.Info().Str("addr", a.Config.ListenAddress).Msg("Listening")
5770
err := http.ListenAndServe(a.Config.ListenAddress, nil)
@@ -62,11 +75,18 @@ func (a *App) Start() {
6275

6376
func (a *App) Handler(w http.ResponseWriter, r *http.Request) {
6477
requestStart := time.Now()
78+
requestID := uuid.New().String()
6579

6680
sublogger := a.Logger.With().
67-
Str("request-id", uuid.New().String()).
81+
Str("request-id", requestID).
6882
Logger()
6983

84+
span := trace.SpanFromContext(r.Context())
85+
span.SetAttributes(attribute.String("request-id", requestID))
86+
rootSpanCtx := r.Context()
87+
88+
defer span.End()
89+
7090
registry := prometheus.NewRegistry()
7191

7292
var wg sync.WaitGroup
@@ -76,16 +96,15 @@ func (a *App) Handler(w http.ResponseWriter, r *http.Request) {
7696

7797
for _, querier := range a.Queriers {
7898
wg.Add(1)
79-
go func(querier types.Querier) {
80-
mutex.Lock()
99+
go func(querier types.Querier, ctx context.Context) {
100+
metrics, querierQueryInfos := querier.GetMetrics(ctx)
81101

82-
metrics, querierQueryInfos := querier.GetMetrics()
102+
mutex.Lock()
83103
registry.MustRegister(metrics...)
84104
queryInfos = append(queryInfos, querierQueryInfos...)
85-
86105
mutex.Unlock()
87106
wg.Done()
88-
}(querier)
107+
}(querier, rootSpanCtx)
89108
}
90109

91110
wg.Wait()

pkg/coingecko/coingecko.go

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package coingecko
22

33
import (
4+
"context"
45
"fmt"
56
"main/pkg/config"
67
"main/pkg/http"
78
"main/pkg/types"
89
"strings"
910

11+
"go.opentelemetry.io/otel/trace"
12+
1013
"github.com/rs/zerolog"
1114
)
1215

@@ -16,22 +19,27 @@ type Coingecko struct {
1619
Client *http.Client
1720
Config *config.Config
1821
Logger zerolog.Logger
22+
Tracer trace.Tracer
1923
}
2024

21-
func NewCoingecko(appConfig *config.Config, logger zerolog.Logger) *Coingecko {
25+
func NewCoingecko(appConfig *config.Config, logger zerolog.Logger, tracer trace.Tracer) *Coingecko {
2226
return &Coingecko{
2327
Config: appConfig,
24-
Client: http.NewClient(logger, "coingecko"),
28+
Client: http.NewClient(logger, "coingecko", tracer),
2529
Logger: logger.With().Str("component", "coingecko").Logger(),
30+
Tracer: tracer,
2631
}
2732
}
2833

29-
func (c *Coingecko) FetchPrices(currencies []string) (map[string]float64, types.QueryInfo) {
34+
func (c *Coingecko) FetchPrices(currencies []string, ctx context.Context) (map[string]float64, types.QueryInfo) {
35+
childCtx, span := c.Tracer.Start(ctx, "Querying prices")
36+
defer span.End()
37+
3038
ids := strings.Join(currencies, ",")
3139
url := fmt.Sprintf("https://api.coingecko.com/api/v3/simple/price?ids=%s&vs_currencies=usd", ids)
3240

3341
var response Response
34-
queryInfo, _, err := c.Client.Get(url, &response, types.HTTPPredicateAlwaysPass())
42+
queryInfo, _, err := c.Client.Get(url, &response, types.HTTPPredicateAlwaysPass(), childCtx)
3543
if err != nil {
3644
c.Logger.Error().Err(err).Msg("Could not get rate")
3745
return nil, queryInfo

pkg/config/config.go

+14-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"fmt"
66
"os"
77

8+
"github.com/guregu/null/v5"
9+
810
"github.com/BurntSushi/toml"
911
"github.com/mcuadros/go-defaults"
1012
)
@@ -78,16 +80,25 @@ func (c *Chain) FindDenomByName(denom string) (*DenomInfo, bool) {
7880
}
7981

8082
type Config struct {
81-
LogConfig LogConfig `toml:"log"`
82-
ListenAddress string `default:":9550" toml:"listen-address"`
83-
Chains []Chain `toml:"chains"`
83+
TracingConfig TracingConfig `toml:"tracing"`
84+
LogConfig LogConfig `toml:"log"`
85+
ListenAddress string `default:":9550" toml:"listen-address"`
86+
Chains []Chain `toml:"chains"`
8487
}
8588

8689
type LogConfig struct {
8790
LogLevel string `default:"info" toml:"level"`
8891
JSONOutput bool `default:"false" toml:"json"`
8992
}
9093

94+
type TracingConfig struct {
95+
Enabled null.Bool `default:"false" toml:"enabled"`
96+
OpenTelemetryHTTPHost string `toml:"open-telemetry-http-host"`
97+
OpenTelemetryHTTPInsecure null.Bool `default:"true" toml:"open-telemetry-http-insecure"`
98+
OpenTelemetryHTTPUser string `toml:"open-telemetry-http-user"`
99+
OpenTelemetryHTTPPassword string `toml:"open-telemetry-http-password"`
100+
}
101+
91102
func (c *Config) Validate() error {
92103
if len(c.Chains) == 0 {
93104
return errors.New("no chains provided")

pkg/http/http.go

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,45 @@
11
package http
22

33
import (
4+
"context"
45
"encoding/json"
56
"main/pkg/types"
67
"net/http"
78
"time"
89

10+
"go.opentelemetry.io/otel/trace"
11+
912
"github.com/rs/zerolog"
13+
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
1014
)
1115

1216
type Client struct {
1317
logger zerolog.Logger
1418
chain string
19+
tracer trace.Tracer
1520
}
1621

17-
func NewClient(logger zerolog.Logger, chain string) *Client {
22+
func NewClient(logger zerolog.Logger, chain string, tracer trace.Tracer) *Client {
1823
return &Client{
1924
logger: logger.With().Str("component", "http").Logger(),
2025
chain: chain,
26+
tracer: tracer,
2127
}
2228
}
2329

2430
func (c *Client) Get(
2531
url string,
2632
target interface{},
2733
predicate types.HTTPPredicate,
34+
ctx context.Context,
2835
) (types.QueryInfo, http.Header, error) {
29-
client := &http.Client{Timeout: 10 * 1000000000}
36+
childCtx, span := c.tracer.Start(ctx, "HTTP request")
37+
defer span.End()
38+
39+
client := &http.Client{
40+
Timeout: 10 * 1000000000,
41+
Transport: otelhttp.NewTransport(http.DefaultTransport),
42+
}
3043
start := time.Now()
3144

3245
queryInfo := types.QueryInfo{
@@ -35,7 +48,7 @@ func (c *Client) Get(
3548
URL: url,
3649
}
3750

38-
req, err := http.NewRequest(http.MethodGet, url, nil)
51+
req, err := http.NewRequestWithContext(childCtx, http.MethodGet, url, nil)
3952
if err != nil {
4053
return queryInfo, nil, err
4154
}

pkg/queriers/balance.go

+22-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package queriers
22

33
import (
4+
"context"
45
"main/pkg/config"
56
"main/pkg/tendermint"
67
"main/pkg/types"
78
"main/pkg/utils"
89
"sync"
910

11+
"go.opentelemetry.io/otel/attribute"
12+
"go.opentelemetry.io/otel/trace"
13+
1014
"github.com/prometheus/client_golang/prometheus"
1115
"github.com/rs/zerolog"
1216
)
@@ -15,23 +19,32 @@ type BalanceQuerier struct {
1519
Config *config.Config
1620
Logger zerolog.Logger
1721
RPCs []*tendermint.RPC
22+
Tracer trace.Tracer
1823
}
1924

20-
func NewBalanceQuerier(config *config.Config, logger zerolog.Logger) *BalanceQuerier {
25+
func NewBalanceQuerier(
26+
config *config.Config,
27+
logger zerolog.Logger,
28+
tracer trace.Tracer,
29+
) *BalanceQuerier {
2130
rpcs := make([]*tendermint.RPC, len(config.Chains))
2231

2332
for index, chain := range config.Chains {
24-
rpcs[index] = tendermint.NewRPC(chain, logger)
33+
rpcs[index] = tendermint.NewRPC(chain, logger, tracer)
2534
}
2635

2736
return &BalanceQuerier{
2837
Config: config,
2938
Logger: logger.With().Str("component", "balance_querier").Logger(),
3039
RPCs: rpcs,
40+
Tracer: tracer,
3141
}
3242
}
3343

34-
func (q *BalanceQuerier) GetMetrics() ([]prometheus.Collector, []types.QueryInfo) {
44+
func (q *BalanceQuerier) GetMetrics(ctx context.Context) ([]prometheus.Collector, []types.QueryInfo) {
45+
childCtx, span := q.Tracer.Start(ctx, "Querying balance metrics")
46+
defer span.End()
47+
3548
balancesGauge := prometheus.NewGaugeVec(
3649
prometheus.GaugeOpts{
3750
Name: "cosmos_wallets_exporter_balance",
@@ -51,9 +64,14 @@ func (q *BalanceQuerier) GetMetrics() ([]prometheus.Collector, []types.QueryInfo
5164
for _, wallet := range chain.Wallets {
5265
wg.Add(1)
5366
go func(wallet config.Wallet, chain config.Chain, rpc *tendermint.RPC) {
67+
chainCtx, chainSpan := q.Tracer.Start(childCtx, "Querying chain and wallet")
68+
chainSpan.SetAttributes(attribute.String("chain", chain.Name))
69+
chainSpan.SetAttributes(attribute.String("wallet", wallet.Address))
70+
defer chainSpan.End()
71+
5472
defer wg.Done()
5573

56-
balancesResponse, queryInfo, err := rpc.GetWalletBalances(wallet.Address)
74+
balancesResponse, queryInfo, err := rpc.GetWalletBalances(wallet.Address, chainCtx)
5775

5876
mutex.Lock()
5977
defer mutex.Unlock()

0 commit comments

Comments
 (0)