Skip to content

Commit

Permalink
Merge pull request #99 from canonical/otel/integration
Browse files Browse the repository at this point in the history
IAM-330: otel/integration
  • Loading branch information
shipperizer authored Jul 26, 2023
2 parents d55aedf + 0b5f248 commit 11d3543
Show file tree
Hide file tree
Showing 11 changed files with 460 additions and 65 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ Code dealing with the environment variables resides in [here](internal/config/sp

At the moment the application is sourcing the following from the environment:

* JAEGER_ENDPOINT - needed if we want to use the otel jeager exporter for traces
* OTEL_GRPC_ENDPOINT - needed if we want to use the otel grpc exporter for traces
* OTEL_HTTP_ENDPOINT - needed if we want to use the otel http exporter for traces (if grpc is specified this gets unused)
* TRACING_ENABLED - switch for tracing, defaults to enabled (`true`)
* LOG_LEVEL - log level, defaults to `error`
* LOG_FILE - log file which the log rotator will write into, *make sure application user has permissions to write*, defaults to `log.txt`
Expand Down
4 changes: 3 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ func main() {

logger := logging.NewLogger(specs.LogLevel, specs.LogFile)

logger.Debugf("env vars: %v", specs)

monitor := prometheus.NewMonitor("identity-login-ui", logger)
tracer := tracing.NewTracer(tracing.NewConfig(specs.TracingEnabled, specs.JaegerEndpoint, logger))
tracer := tracing.NewTracer(tracing.NewConfig(specs.TracingEnabled, specs.OtelGRPCEndpoint, specs.OtelHTTPEndpoint, logger))

distFS, err := fs.Sub(jsFS, "ui/dist")

Expand Down
15 changes: 11 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ require (
github.com/prometheus/client_golang v1.16.0
github.com/stretchr/testify v1.8.4
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0
go.opentelemetry.io/contrib/propagators/jaeger v1.17.0
go.opentelemetry.io/otel v1.16.0
go.opentelemetry.io/otel/exporters/jaeger v1.16.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp 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
Expand All @@ -23,12 +26,14 @@ require (

require (
github.com/beorn7/perks v1.0.1 // 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/felixge/httpsnoop v1.0.3 // 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.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
Expand All @@ -37,16 +42,18 @@ require (
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.10.1 // 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/metric v1.16.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/mod v0.4.2 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/oauth2 v0.7.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/tools v0.1.1 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
google.golang.org/grpc v1.55.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
408 changes: 389 additions & 19 deletions go.sum

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions internal/config/specs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package config

// EnvSpec is the basic environment configuration setup needed for the app to start
type EnvSpec struct {
JaegerEndpoint string `envconfig:"jaeger_endpoint"`
TracingEnabled bool `envconfig:"tracing_enabled" default:"true"`
OtelGRPCEndpoint string `envconfig:"otel_grpc_endpoint"`
OtelHTTPEndpoint string `envconfig:"otel_http_endpoint"`
TracingEnabled bool `envconfig:"tracing_enabled" default:"true"`

LogLevel string `envconfig:"log_level" default:"error"`
LogFile string `envconfig:"log_file" default:"log.txt"`
Expand Down
10 changes: 6 additions & 4 deletions internal/tracing/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ import (
)

type Config struct {
JaegerEndpoint string
Logger logging.LoggerInterface
OtelHTTPEndpoint string
OtelGRPCEndpoint string
Logger logging.LoggerInterface

Enabled bool
}

func NewConfig(enabled bool, endpoint string, logger logging.LoggerInterface) *Config {
func NewConfig(enabled bool, otelGRPCEndpoint, otelHTTPEndpoint string, logger logging.LoggerInterface) *Config {
c := new(Config)

c.JaegerEndpoint = endpoint
c.OtelGRPCEndpoint = otelGRPCEndpoint
c.OtelHTTPEndpoint = otelHTTPEndpoint
c.Logger = logger
c.Enabled = enabled

Expand Down
26 changes: 19 additions & 7 deletions internal/tracing/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import (
"runtime/debug"

"github.com/canonical/identity-platform-login-ui/internal/logging"
"go.opentelemetry.io/contrib/propagators/jaeger"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
Expand All @@ -32,7 +35,7 @@ func (t *Tracer) init(service string, e sdktrace.SpanExporter) {
)

otel.SetTracerProvider(traceProvider)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}, jaeger.Jaeger{}))

t.tracer = otel.Tracer(service)
}
Expand Down Expand Up @@ -92,11 +95,20 @@ func NewTracer(cfg *Config) *Tracer {
var err error
var exporter sdktrace.SpanExporter

// create jaeger exporter
if cfg.JaegerEndpoint != "" {
exporter, err = jaeger.New(
jaeger.WithCollectorEndpoint(
jaeger.WithEndpoint(cfg.JaegerEndpoint),
if cfg.OtelGRPCEndpoint != "" {
exporter, err = otlptrace.New(
context.TODO(),
otlptracegrpc.NewClient(
otlptracegrpc.WithEndpoint(cfg.OtelGRPCEndpoint),
otlptracegrpc.WithInsecure(),
),
)
} else if cfg.OtelHTTPEndpoint != "" {
exporter, err = otlptrace.New(
context.TODO(),
otlptracehttp.NewClient(
otlptracehttp.WithEndpoint(cfg.OtelHTTPEndpoint),
otlptracehttp.WithInsecure(),
),
)
} else {
Expand Down
6 changes: 3 additions & 3 deletions pkg/extra/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type Service struct {
}

func (s *Service) CheckSession(ctx context.Context, cookies []*http.Cookie) (*kClient.Session, error) {
_, span := s.tracer.Start(ctx, "kratos.FrontendApi.ToSession")
ctx, span := s.tracer.Start(ctx, "kratos.FrontendApi.ToSession")
defer span.End()

session, r, err := s.kratos.FrontendApi().ToSession(
Expand All @@ -42,7 +42,7 @@ func (s *Service) CheckSession(ctx context.Context, cookies []*http.Cookie) (*kC
}

func (s *Service) GetConsent(ctx context.Context, challenge string) (*hClient.OAuth2ConsentRequest, error) {
_, span := s.tracer.Start(ctx, "hydra.OAuth2Api.GetOAuth2ConsentRequest")
ctx, span := s.tracer.Start(ctx, "hydra.OAuth2Api.GetOAuth2ConsentRequest")
defer span.End()

consent, res, err := s.hydra.OAuth2Api().GetOAuth2ConsentRequest(
Expand All @@ -68,7 +68,7 @@ func (s *Service) AcceptConsent(ctx context.Context, identity kClient.Identity,
r.SetGrantAccessTokenAudience(consent.RequestedAccessTokenAudience)
r.SetSession(*session)

_, span := s.tracer.Start(ctx, "hydra.OAuth2Api.AcceptOAuth2ConsentRequest")
ctx, span := s.tracer.Start(ctx, "hydra.OAuth2Api.AcceptOAuth2ConsentRequest")
defer span.End()

accept, res, err := s.hydra.OAuth2Api().AcceptOAuth2ConsentRequest(
Expand Down
12 changes: 6 additions & 6 deletions pkg/extra/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestCheckSessionSuccess(t *testing.T) {
ApiService: mockKratosFrontendApi,
}

mockTracer.EXPECT().Start(ctx, "kratos.FrontendApi.ToSession").Times(1).Return(nil, trace.SpanFromContext(ctx))
mockTracer.EXPECT().Start(ctx, "kratos.FrontendApi.ToSession").Times(1).Return(ctx, trace.SpanFromContext(ctx))
mockKratos.EXPECT().FrontendApi().Times(1).Return(mockKratosFrontendApi)
mockKratosFrontendApi.EXPECT().ToSession(ctx).Times(1).Return(sessionRequest)
mockKratosFrontendApi.EXPECT().ToSessionExecute(gomock.Any()).Times(1).DoAndReturn(
Expand Down Expand Up @@ -84,7 +84,7 @@ func TestCheckSessionFails(t *testing.T) {
}

mockLogger.EXPECT().Debugf(gomock.Any(), gomock.Any()).Times(1)
mockTracer.EXPECT().Start(ctx, "kratos.FrontendApi.ToSession").Times(1).Return(nil, trace.SpanFromContext(ctx))
mockTracer.EXPECT().Start(ctx, "kratos.FrontendApi.ToSession").Times(1).Return(ctx, trace.SpanFromContext(ctx))
mockKratos.EXPECT().FrontendApi().Times(1).Return(mockKratosFrontendApi)
mockKratosFrontendApi.EXPECT().ToSession(ctx).Times(1).Return(sessionRequest)
mockKratosFrontendApi.EXPECT().ToSessionExecute(gomock.Any()).Times(1).DoAndReturn(
Expand Down Expand Up @@ -126,7 +126,7 @@ func TestGetConsentSuccess(t *testing.T) {
ApiService: mockHydraOAuth2Api,
}
consent := hClient.NewOAuth2ConsentRequest(challengeString)
mockTracer.EXPECT().Start(ctx, "hydra.OAuth2Api.GetOAuth2ConsentRequest").Times(1).Return(nil, trace.SpanFromContext(ctx))
mockTracer.EXPECT().Start(ctx, "hydra.OAuth2Api.GetOAuth2ConsentRequest").Times(1).Return(ctx, trace.SpanFromContext(ctx))
mockHydra.EXPECT().OAuth2Api().Times(1).Return(mockHydraOAuth2Api)
mockHydraOAuth2Api.EXPECT().GetOAuth2ConsentRequest(ctx).Times(1).Return(consentRequest)
mockHydraOAuth2Api.EXPECT().GetOAuth2ConsentRequestExecute(gomock.Any()).Times(1).DoAndReturn(
Expand Down Expand Up @@ -167,7 +167,7 @@ func TestGetConsentFails(t *testing.T) {
ApiService: mockHydraOAuth2Api,
}
mockLogger.EXPECT().Debugf(gomock.Any(), gomock.Any()).Times(1)
mockTracer.EXPECT().Start(ctx, "hydra.OAuth2Api.GetOAuth2ConsentRequest").Times(1).Return(nil, trace.SpanFromContext(ctx))
mockTracer.EXPECT().Start(ctx, "hydra.OAuth2Api.GetOAuth2ConsentRequest").Times(1).Return(ctx, trace.SpanFromContext(ctx))
mockHydra.EXPECT().OAuth2Api().Times(1).Return(mockHydraOAuth2Api)
mockHydraOAuth2Api.EXPECT().GetOAuth2ConsentRequest(ctx).Times(1).Return(consentRequest)
mockHydraOAuth2Api.EXPECT().GetOAuth2ConsentRequestExecute(gomock.Any()).Times(1).DoAndReturn(
Expand Down Expand Up @@ -211,7 +211,7 @@ func TestAcceptConsentSuccess(t *testing.T) {
consent := hClient.NewOAuth2ConsentRequest("test.challenge")
identity := kClient.NewIdentity("test", "test.json", "https://test.com/test.json", map[string]string{"name": "name"})
accept := hClient.NewOAuth2RedirectTo(redirect)
mockTracer.EXPECT().Start(ctx, "hydra.OAuth2Api.AcceptOAuth2ConsentRequest").Times(1).Return(nil, trace.SpanFromContext(ctx))
mockTracer.EXPECT().Start(ctx, "hydra.OAuth2Api.AcceptOAuth2ConsentRequest").Times(1).Return(ctx, trace.SpanFromContext(ctx))
mockHydra.EXPECT().OAuth2Api().Times(1).Return(mockHydraOAuth2Api)
mockHydraOAuth2Api.EXPECT().AcceptOAuth2ConsentRequest(ctx).Times(1).Return(acceptRequest)
mockHydraOAuth2Api.EXPECT().AcceptOAuth2ConsentRequestExecute(gomock.Any()).Times(1).DoAndReturn(
Expand Down Expand Up @@ -265,7 +265,7 @@ func TestAcceptConsentFails(t *testing.T) {
identity := kClient.NewIdentity("test", "test.json", "https://test.com/test.json", map[string]string{"name": "name"})

mockLogger.EXPECT().Debugf(gomock.Any(), gomock.Any()).Times(1)
mockTracer.EXPECT().Start(ctx, "hydra.OAuth2Api.AcceptOAuth2ConsentRequest").Times(1).Return(nil, trace.SpanFromContext(ctx))
mockTracer.EXPECT().Start(ctx, "hydra.OAuth2Api.AcceptOAuth2ConsentRequest").Times(1).Return(ctx, trace.SpanFromContext(ctx))
mockHydra.EXPECT().OAuth2Api().Times(1).Return(mockHydraOAuth2Api)
mockHydraOAuth2Api.EXPECT().AcceptOAuth2ConsentRequest(ctx).Times(1).Return(acceptRequest)
mockHydraOAuth2Api.EXPECT().AcceptOAuth2ConsentRequestExecute(gomock.Any()).Times(1).DoAndReturn(
Expand Down
12 changes: 6 additions & 6 deletions pkg/kratos/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type ErrorBrowserLocationChangeRequired struct {
}

func (s *Service) CheckSession(ctx context.Context, cookies []*http.Cookie) (*kClient.Session, []*http.Cookie, error) {
_, span := s.tracer.Start(ctx, "kratos.FrontendApi.ToSession")
ctx, span := s.tracer.Start(ctx, "kratos.FrontendApi.ToSession")
defer span.End()

session, resp, err := s.kratos.FrontendApi().
Expand All @@ -50,7 +50,7 @@ func (s *Service) CheckSession(ctx context.Context, cookies []*http.Cookie) (*kC
}

func (s *Service) AcceptLoginRequest(ctx context.Context, identityID string, lc string) (*hClient.OAuth2RedirectTo, []*http.Cookie, error) {
_, span := s.tracer.Start(ctx, "hydra.OAuth2Api.AcceptOAuth2LoginRequest")
ctx, span := s.tracer.Start(ctx, "hydra.OAuth2Api.AcceptOAuth2LoginRequest")
defer span.End()

accept := hClient.NewAcceptOAuth2LoginRequest(identityID)
Expand All @@ -72,7 +72,7 @@ func (s *Service) AcceptLoginRequest(ctx context.Context, identityID string, lc
func (s *Service) CreateBrowserLoginFlow(
ctx context.Context, aal, returnTo, loginChallenge string, refresh bool, cookies []*http.Cookie,
) (*kClient.LoginFlow, []*http.Cookie, error) {
_, span := s.tracer.Start(ctx, "kratos.FrontendApi.CreateBrowserLoginFlow")
ctx, span := s.tracer.Start(ctx, "kratos.FrontendApi.CreateBrowserLoginFlow")
defer span.End()

flow, resp, err := s.kratos.FrontendApi().
Expand All @@ -92,7 +92,7 @@ func (s *Service) CreateBrowserLoginFlow(
}

func (s *Service) GetLoginFlow(ctx context.Context, id string, cookies []*http.Cookie) (*kClient.LoginFlow, []*http.Cookie, error) {
_, span := s.tracer.Start(ctx, "kratos.FrontendApi.GetLoginFlow")
ctx, span := s.tracer.Start(ctx, "kratos.FrontendApi.GetLoginFlow")
defer span.End()

flow, resp, err := s.kratos.FrontendApi().
Expand All @@ -111,7 +111,7 @@ func (s *Service) GetLoginFlow(ctx context.Context, id string, cookies []*http.C
func (s *Service) UpdateOIDCLoginFlow(
ctx context.Context, flow string, body kClient.UpdateLoginFlowBody, cookies []*http.Cookie,
) (*ErrorBrowserLocationChangeRequired, []*http.Cookie, error) {
_, span := s.tracer.Start(ctx, "kratos.FrontendApi.UpdateLoginFlow")
ctx, span := s.tracer.Start(ctx, "kratos.FrontendApi.UpdateLoginFlow")
defer span.End()

_, resp, err := s.kratos.FrontendApi().
Expand All @@ -135,7 +135,7 @@ func (s *Service) UpdateOIDCLoginFlow(
}

func (s *Service) GetFlowError(ctx context.Context, id string) (*kClient.FlowError, []*http.Cookie, error) {
_, span := s.tracer.Start(ctx, "kratos.FrontendApi.GetFlowError")
ctx, span := s.tracer.Start(ctx, "kratos.FrontendApi.GetFlowError")
defer span.End()

flowError, resp, err := s.kratos.FrontendApi().GetFlowError(context.Background()).Id(id).Execute()
Expand Down
Loading

0 comments on commit 11d3543

Please sign in to comment.