Skip to content

Commit

Permalink
feat: track external client logs (#183)
Browse files Browse the repository at this point in the history
* feat: track external client logs

* feat: track external client logs

* fix bigquery provider test

* fix: some test cases

* add gitlab otel

* add oauth scope for each service

* add metric into external client

* refactor otel http client

* coverage test

* coverage test

* coverage test

* increase metric interval to 15s

* fix data race

* fix go mod

* resolve comments

---------

Co-authored-by: Lifosmin Simon <[email protected]>
  • Loading branch information
lifosmin and Lifosmin Simon authored Nov 1, 2024
1 parent 8ac4398 commit 8f8b145
Show file tree
Hide file tree
Showing 29 changed files with 417 additions and 72 deletions.
4 changes: 2 additions & 2 deletions core/appeal/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -1578,9 +1578,9 @@ func (s *Service) populateAppealMetadata(ctx context.Context, a *domain.Appeal,
if err != nil {
return err
}
clientCreator := &http.HttpClientCreatorStruct{}
metadataCl, err := http.NewHTTPClient(&cfg.HTTPClientConfig, clientCreator)

clientCreator := &http.HttpClientCreatorStruct{}
metadataCl, err := http.NewHTTPClient(&cfg.HTTPClientConfig, clientCreator, "AppealMetadata")
if err != nil {
return fmt.Errorf("key: %s, %w", key, err)
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0
go.opentelemetry.io/otel/metric v1.29.0
go.opentelemetry.io/otel/sdk v1.29.0
go.opentelemetry.io/otel/sdk/metric v1.27.0
golang.org/x/net v0.22.0
Expand Down Expand Up @@ -170,7 +171,6 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel/metric v1.29.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
Expand Down
4 changes: 3 additions & 1 deletion pkg/gate/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"net/http"
"net/url"
"strconv"

"github.com/goto/guardian/pkg/opentelemetry/otelhttpclient"
)

type Client struct {
Expand All @@ -25,7 +27,7 @@ func NewClient(baseURL string, opts ...ClientOption) (*Client, error) {
client := &Client{
baseURL: url,
options: &options{
httpClient: http.DefaultClient,
httpClient: otelhttpclient.New("GateClient", nil),
},
}
for _, o := range opts {
Expand Down
6 changes: 5 additions & 1 deletion pkg/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"

validator "github.com/go-playground/validator/v10"
"github.com/goto/guardian/pkg/opentelemetry/otelhttpclient"
defaults "github.com/mcuadros/go-defaults"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
Expand Down Expand Up @@ -61,7 +62,7 @@ type HttpClientCreator interface {
}

// NewHTTPClient returns *iam.Client
func NewHTTPClient(config *HTTPClientConfig, clientCreator HttpClientCreator) (*HTTPClient, error) {
func NewHTTPClient(config *HTTPClientConfig, clientCreator HttpClientCreator, serviceName string) (*HTTPClient, error) {
defaults.SetDefaults(config)
if err := validator.New().Struct(config); err != nil {
return nil, err
Expand All @@ -70,6 +71,9 @@ func NewHTTPClient(config *HTTPClientConfig, clientCreator HttpClientCreator) (*
if httpClient == nil {
httpClient = http.DefaultClient
}
if serviceName != "" {
httpClient = otelhttpclient.New(serviceName, nil)
}

if config.Auth != nil && (config.Auth.Type == "google_idtoken" || config.Auth.Type == "google_oauth2") {
var creds []byte
Expand Down
24 changes: 12 additions & 12 deletions pkg/http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestMakeRequestForGet(t *testing.T) {
Value: "test_value",
},
Method: "GET",
}, nil)
}, nil, "metabase")
if err != nil {
t.Fatalf("Failed to create HTTPClient: %v", err)
}
Expand Down Expand Up @@ -78,7 +78,7 @@ func TestMakeRequestForPostWithPayload(t *testing.T) {
},
Method: "POST",
Body: "test",
}, nil)
}, nil, "metabase")
if err != nil {
t.Fatalf("Failed to create HTTPClient: %v", err)
}
Expand Down Expand Up @@ -121,7 +121,7 @@ func TestMakeRequestForPostWithoutPayload(t *testing.T) {
Token: "test_token",
},
Method: "POST",
}, nil)
}, nil, "metabase")
if err != nil {
t.Fatalf("Failed to create HTTPClient: %v", err)
}
Expand Down Expand Up @@ -167,7 +167,7 @@ func TestMakeRequestForPostWithPayloadForHeaderAuth(t *testing.T) {
},
Method: "POST",
Body: "test",
}, nil)
}, nil, "metabase")
if err != nil {
t.Fatalf("Failed to create HTTPClient: %v", err)
}
Expand Down Expand Up @@ -210,7 +210,7 @@ func TestMakeRequestForPostWithPayloadForBasicAuth(t *testing.T) {
},
Method: "POST",
Body: "test",
}, nil)
}, nil, "metabase")
if err != nil {
t.Fatalf("Failed to create HTTPClient: %v", err)
}
Expand Down Expand Up @@ -260,7 +260,7 @@ func TestNewHTTPClient_GoogleOAuth2(t *testing.T) {
expectedClient := &http.Client{}

mockCreator := new(MockHttpClientCreator)
mockCreator.On("GetHttpClientForGoogleOAuth2", mock.Anything, []byte(credsJSON)).Return(expectedClient, nil)
mockCreator.On("GetHttpClientForGoogleOAuth2", mock.Anything, []byte(credsJSON)).Return(expectedClient, nil, "metabase")

config := &HTTPClientConfig{
URL: "https://example.com",
Expand All @@ -270,7 +270,7 @@ func TestNewHTTPClient_GoogleOAuth2(t *testing.T) {
},
}

client, err := NewHTTPClient(config, mockCreator)
client, err := NewHTTPClient(config, mockCreator, "metabase")

assert.NoError(t, err)
assert.NotNil(t, client)
Expand All @@ -288,7 +288,7 @@ func TestNewHTTPClient_GoogleOAuth2WithEmptyCredentialJson(t *testing.T) {
},
}

client, err := NewHTTPClient(config, mockCreator)
client, err := NewHTTPClient(config, mockCreator, "metabase")

assert.Error(t, err, "missing credentials for google_idtoken or google_oauth2 auth")
assert.Nil(t, client)
Expand All @@ -301,7 +301,7 @@ func TestNewHTTPClient_GoogleIdToken(t *testing.T) {
expectedClient := &http.Client{}

mockCreator := new(MockHttpClientCreator)
mockCreator.On("GetHttpClientForGoogleIdToken", mock.Anything, []byte(credsJSON), "audience").Return(expectedClient, nil)
mockCreator.On("GetHttpClientForGoogleIdToken", mock.Anything, []byte(credsJSON), "audience").Return(expectedClient, nil, "metabase")

config := &HTTPClientConfig{
URL: "https://example.com",
Expand All @@ -312,7 +312,7 @@ func TestNewHTTPClient_GoogleIdToken(t *testing.T) {
},
}

client, err := NewHTTPClient(config, mockCreator)
client, err := NewHTTPClient(config, mockCreator, "metabase")

assert.NoError(t, err)
assert.NotNil(t, client)
Expand All @@ -336,7 +336,7 @@ func TestNewHTTPClient_GoogleIdTokenErrorScenario(t *testing.T) {
},
}

_, err := NewHTTPClient(config, mockCreator)
_, err := NewHTTPClient(config, mockCreator, "metabase")

assert.Equal(t, err.Error(), "error creating http client for google_idtoken")
mockCreator.AssertExpectations(t)
Expand All @@ -358,7 +358,7 @@ func TestNewHTTPClient_GoogleOAuth2ErrorScenario(t *testing.T) {
},
}

_, err := NewHTTPClient(config, mockCreator)
_, err := NewHTTPClient(config, mockCreator, "metabase")

assert.Equal(t, err.Error(), "error creating http client for google_oauth2")
mockCreator.AssertExpectations(t)
Expand Down
2 changes: 1 addition & 1 deletion pkg/opentelemetry/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ type Config struct {
Endpoint string `mapstructure:"endpoint" default:"127.0.0.1:4317"`
} `mapstructure:"otlp"`
SamplingFraction int `mapstructure:"sampling_fraction"`
MetricInterval time.Duration `mapstructure:"metric_interval"`
MetricInterval time.Duration `mapstructure:"metric_interval" default:"15s"`
}
24 changes: 8 additions & 16 deletions pkg/opentelemetry/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@ package opentelemetry
import (
"context"
"fmt"
"net/http"
"time"

"go.opentelemetry.io/contrib/instrumentation/host"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/contrib/instrumentation/runtime"
"go.opentelemetry.io/otel"

"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/metric"
sdkMetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
Expand Down Expand Up @@ -83,8 +82,8 @@ func initGlobalMetrics(ctx context.Context, res *resource.Resource, cfg Config)
return nil, fmt.Errorf("create metric exporter: %w", err)
}

reader := metric.NewPeriodicReader(exporter, metric.WithInterval(cfg.MetricInterval))
provider := metric.NewMeterProvider(metric.WithReader(reader), metric.WithResource(res))
reader := sdkMetric.NewPeriodicReader(exporter, sdkMetric.WithInterval(cfg.MetricInterval))
provider := sdkMetric.NewMeterProvider(sdkMetric.WithReader(reader), sdkMetric.WithResource(res))
otel.SetMeterProvider(provider)

return func() error {
Expand All @@ -107,6 +106,10 @@ func initGlobalTracer(ctx context.Context, res *resource.Resource, cfg Config) (
return nil, fmt.Errorf("create trace exporter: %w", err)
}

if err != nil {
return nil, fmt.Errorf("create stdout trace exporter: %w", err)
}

tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithResource(res),
Expand All @@ -127,14 +130,3 @@ func initGlobalTracer(ctx context.Context, res *resource.Resource, cfg Config) (
return nil
}, nil
}

func NewHttpClient(name string) *http.Client {
opts := []otelhttp.Option{
otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string {
return fmt.Sprintf("%s %s", name, operation)
}),
}
return &http.Client{
Transport: otelhttp.NewTransport(http.DefaultTransport, opts...),
}
}
35 changes: 35 additions & 0 deletions pkg/opentelemetry/otelhttpclient/annotations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package otelhttpclient

import (
"context"
"net/http"

"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/attribute"
)

type labelerContextKeyType int

const lablelerContextKey labelerContextKeyType = 0

// AnnotateRequest adds telemetry related annotations to request context and returns.
// The request context on the returned request should be retained.
// Ensure `route` is a route template and not actual URL to prevent high cardinality
// on the metrics.
func AnnotateRequest(req *http.Request, route string) *http.Request {
ctx := req.Context()

l := &otelhttp.Labeler{}
l.Add(attribute.String(attributeHTTPRoute, route))

return req.WithContext(context.WithValue(ctx, lablelerContextKey, l))
}

// LabelerFromContext returns the labeler annotation from the context if exists.
func LabelerFromContext(ctx context.Context) (*otelhttp.Labeler, bool) {
l, ok := ctx.Value(lablelerContextKey).(*otelhttp.Labeler)
if !ok {
l = &otelhttp.Labeler{}
}
return l, ok
}
15 changes: 15 additions & 0 deletions pkg/opentelemetry/otelhttpclient/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package otelhttpclient

import (
"net/http"
)

func New(name string, client *http.Client) *http.Client {
if client == nil {
return &http.Client{
Transport: NewHTTPTransport(nil, name),
}
}
client.Transport = NewHTTPTransport(client.Transport, name)
return client
}
Loading

0 comments on commit 8f8b145

Please sign in to comment.