Skip to content

Commit

Permalink
Merge pull request #218 from LerianStudio/feature/MZ-625
Browse files Browse the repository at this point in the history
Feature/MZ-625
  • Loading branch information
MartinezAvellan authored Nov 11, 2024
2 parents 3d5ea79 + c5861e7 commit 4a67e71
Show file tree
Hide file tree
Showing 86 changed files with 1,656 additions and 415 deletions.
65 changes: 65 additions & 0 deletions common/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package common

import (
"context"
"github.com/LerianStudio/midaz/common/mlog"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
)

type customContextKey string

var CustomContextKey = customContextKey("custom_context")

type CustomContextKeyValue struct {
Tracer trace.Tracer
Logger mlog.Logger
}

// NewLoggerFromContext extract the Logger from "logger" value inside context
//
//nolint:ireturn
func NewLoggerFromContext(ctx context.Context) mlog.Logger {
if customContext, ok := ctx.Value(CustomContextKey).(*CustomContextKeyValue); ok &&
customContext.Logger != nil {
return customContext.Logger
}

return &mlog.NoneLogger{}
}

// ContextWithLogger returns a context within a Logger in "logger" value.
func ContextWithLogger(ctx context.Context, logger mlog.Logger) context.Context {
values, _ := ctx.Value(CustomContextKey).(*CustomContextKeyValue)
if values == nil {
values = &CustomContextKeyValue{}
}

values.Logger = logger

return context.WithValue(ctx, CustomContextKey, values)
}

// NewTracerFromContext returns a new tracer from the context.
//
//nolint:ireturn
func NewTracerFromContext(ctx context.Context) trace.Tracer {
if customContext, ok := ctx.Value(CustomContextKey).(*CustomContextKeyValue); ok &&
customContext.Tracer != nil {
return customContext.Tracer
}

return otel.Tracer("default")
}

// ContextWithTracer returns a context within a trace.Tracer in "tracer" value.
func ContextWithTracer(ctx context.Context, tracer trace.Tracer) context.Context {
values, _ := ctx.Value(CustomContextKey).(*CustomContextKeyValue)
if values == nil {
values = &CustomContextKeyValue{}
}

values.Tracer = tracer

return context.WithValue(ctx, CustomContextKey, values)
}
21 changes: 0 additions & 21 deletions common/mlog/log.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package mlog

import (
"context"
"fmt"
"log"
"strings"
Expand Down Expand Up @@ -207,23 +206,3 @@ func (l *GoLogger) WithFields(fields ...any) Logger {
//
//nolint:ireturn
func (l *GoLogger) Sync() error { return nil }

// NewLoggerFromContext extract the Logger from "logger" value inside context
//
//nolint:ireturn
func NewLoggerFromContext(ctx context.Context) Logger {
if logger := ctx.Value(loggerContextKey("logger")); logger != nil {
if l, ok := logger.(Logger); ok {
return l
}
}

return &NoneLogger{}
}

type loggerContextKey string

// ContextWithLogger returns a context within a Logger in "logger" value.
func ContextWithLogger(ctx context.Context, logger Logger) context.Context {
return context.WithValue(ctx, loggerContextKey("logger"), logger)
}
25 changes: 1 addition & 24 deletions common/mopentelemetry/otel.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
"go.opentelemetry.io/otel/trace"
"os"
"time"
)

type Telemetry struct {
Expand Down Expand Up @@ -93,9 +92,7 @@ func (tl *Telemetry) NewLoggerProvider(rsc *sdkresource.Resource, exp *otlploggr
func (tl *Telemetry) newMeterProvider(res *sdkresource.Resource, exp *otlpmetricgrpc.Exporter) *sdkmetric.MeterProvider {
mp := sdkmetric.NewMeterProvider(
sdkmetric.WithResource(res),
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exp,
// TODO: (REMOVE THIS) Default is 1m. Set to 5s for development purposes.
sdkmetric.WithInterval(5*time.Second))),
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exp)),
)

return mp
Expand Down Expand Up @@ -174,26 +171,6 @@ func (tl *Telemetry) InitializeTelemetry() *Telemetry {
}
}

// NewTracerFromContext returns a new tracer from the context.
//
//nolint:ireturn
func NewTracerFromContext(ctx context.Context) trace.Tracer {
if tracer := ctx.Value(tracerContextKey("tracer")); tracer != nil {
if l, ok := tracer.(trace.Tracer); ok {
return l
}
}

return otel.Tracer("default")
}

type tracerContextKey string

// ContextWithTracer returns a context within a trace.Tracer in "tracer" value.
func ContextWithTracer(ctx context.Context, tracer trace.Tracer) context.Context {
return context.WithValue(ctx, tracerContextKey("tracer"), tracer)
}

// SetSpanAttributesFromStruct converts a struct to a JSON string and sets it as an attribute on the span.
func SetSpanAttributesFromStruct(span *trace.Span, key string, valueStruct any) error {
vStr, err := common.StructToJSONString(valueStruct)
Expand Down
11 changes: 5 additions & 6 deletions common/net/http/withJWT.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"github.com/LerianStudio/midaz/common/mcasdoor"
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"

"github.com/LerianStudio/midaz/common/mlog"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt"
"github.com/lestrrat-go/jwx/jwk"
Expand Down Expand Up @@ -222,7 +221,7 @@ func NewJWTMiddleware(cc *mcasdoor.CasdoorConnection) *JWTMiddleware {
// ProtectHTTP protects any endpoint using JWT tokens.
func (jwtm *JWTMiddleware) ProtectHTTP() fiber.Handler {
return func(c *fiber.Ctx) error {
l := mlog.NewLoggerFromContext(c.UserContext())
l := common.NewLoggerFromContext(c.UserContext())
l.Info("JWTMiddleware:ProtectHTTP")

l.Info("Read token from header")
Expand Down Expand Up @@ -270,7 +269,7 @@ func (jwtm *JWTMiddleware) ProtectHTTP() fiber.Handler {
// WithScope verify if a requester has the required scope to access an endpoint.
func (jwtm *JWTMiddleware) WithScope(scopes []string) fiber.Handler {
return func(c *fiber.Ctx) error {
l := mlog.NewLoggerFromContext(c.UserContext())
l := common.NewLoggerFromContext(c.UserContext())
l.Info("JWTMiddleware:WithScope")

parser := TokenParser{
Expand Down Expand Up @@ -309,7 +308,7 @@ func (jwtm *JWTMiddleware) WithScope(scopes []string) fiber.Handler {
// WithPermissionHTTP verify if a requester has the required permission to access an endpoint.
func (jwtm *JWTMiddleware) WithPermissionHTTP(resource string) fiber.Handler {
return func(c *fiber.Ctx) error {
l := mlog.NewLoggerFromContext(c.UserContext())
l := common.NewLoggerFromContext(c.UserContext())
l.Info("JWTMiddleware:WithPermissionHTTP")

client, err := jwtm.connection.GetClient()
Expand Down Expand Up @@ -369,7 +368,7 @@ func (jwtm *JWTMiddleware) ProtectGrpc() grpc.UnaryServerInterceptor {
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (any, error) {
l := mlog.NewLoggerFromContext(ctx)
l := common.NewLoggerFromContext(ctx)
l.Info("JWTMiddleware:ProtectGrpc")

tokenString := getTokenHeaderFromContext(ctx)
Expand Down Expand Up @@ -419,7 +418,7 @@ func (jwtm *JWTMiddleware) WithPermissionGrpc() grpc.UnaryServerInterceptor {
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (any, error) {
l := mlog.NewLoggerFromContext(ctx)
l := common.NewLoggerFromContext(ctx)
l.Info("JWTMiddleware:WithPermissionGrpc")

client, err := jwtm.connection.GetClient()
Expand Down
7 changes: 4 additions & 3 deletions common/net/http/withLogging.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package http

import (
"context"
"github.com/LerianStudio/midaz/common"
"net/url"
"strconv"
"strings"
Expand Down Expand Up @@ -177,9 +178,9 @@ func WithHTTPLogging(opts ...LogMiddlewareOption) fiber.Handler {

logger.Info(info.debugRequestString())

ctx := mlog.ContextWithLogger(c.Context(), logger)
ctx := common.ContextWithLogger(c.UserContext(), logger)

c.SetUserContext(mlog.ContextWithLogger(ctx, logger))
c.SetUserContext(ctx)

info.FinishRequestInfo(&rw)

Expand All @@ -205,7 +206,7 @@ func WithGrpcLogging(opts ...LogMiddlewareOption) grpc.UnaryServerInterceptor {
mid := buildOpts(opts...)
logger := mid.Logger

ctx = mlog.ContextWithLogger(ctx, logger)
ctx = common.ContextWithLogger(ctx, logger)

start := time.Now()
resp, err := handler(ctx, req)
Expand Down
80 changes: 70 additions & 10 deletions common/net/http/withTelemetry.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package http

import (
"context"
"github.com/LerianStudio/midaz/common"
cn "github.com/LerianStudio/midaz/common/constant"
"github.com/LerianStudio/midaz/common/mopentelemetry"
"github.com/gofiber/fiber/v2"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

type TelemetryMiddleware struct {
Expand All @@ -22,13 +27,14 @@ func NewTelemetryMiddleware(tl *mopentelemetry.Telemetry) *TelemetryMiddleware {
func (tm *TelemetryMiddleware) WithTelemetry(tl *mopentelemetry.Telemetry) fiber.Handler {
return func(c *fiber.Ctx) error {
tracer := otel.Tracer(tl.LibraryName)
ctx, _ := tracer.Start(c.Context(), c.Method()+" "+c.Path())
ctx := common.ContextWithTracer(c.UserContext(), tracer)

ctx = mopentelemetry.ContextWithTracer(ctx, tracer)
ctx, span := tracer.Start(ctx, c.Method()+" "+common.ReplaceUUIDWithPlaceholder(c.Path()))
defer span.End()

c.SetUserContext(ctx)

err := tm.collectMetrics(c)
err := tm.collectMetrics(ctx)
if err != nil {
return WithError(c, err)
}
Expand All @@ -38,15 +44,69 @@ func (tm *TelemetryMiddleware) WithTelemetry(tl *mopentelemetry.Telemetry) fiber
}

// EndTracingSpans is a middleware that ends the tracing spans.
func (tm *TelemetryMiddleware) EndTracingSpans() fiber.Handler {
return func(c *fiber.Ctx) error {
trace.SpanFromContext(c.Context()).End()
func (tm *TelemetryMiddleware) EndTracingSpans(c *fiber.Ctx) error {
err := c.Next()

return c.Next()
go func() {
trace.SpanFromContext(c.UserContext()).End()
}()

return err
}

// WithTelemetryInterceptor is a gRPC interceptor that adds tracing to the context.
func (tm *TelemetryMiddleware) WithTelemetryInterceptor(tl *mopentelemetry.Telemetry) grpc.UnaryServerInterceptor {
return func(
ctx context.Context,
req any,
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (any, error) {
tracer := otel.Tracer(tl.LibraryName)
ctx, span := tracer.Start(ctx, info.FullMethod)

ctx = common.ContextWithTracer(ctx, tracer)

err := tm.collectMetrics(ctx)
if err != nil {
e := common.ValidateBusinessError(cn.ErrInternalServer, "Failed to collect metrics")

jsonStringError, err := common.StructToJSONString(e)
if err != nil {
return nil, status.Error(codes.Internal, "Failed to marshal error response")
}

return nil, status.Error(codes.FailedPrecondition, jsonStringError)
}

resp, err := handler(ctx, req)
if err != nil {
mopentelemetry.HandleSpanError(&span, "gRPC request failed", err)
}

return resp, err
}
}

// EndTracingSpansInterceptor is a gRPC interceptor that ends the tracing spans.
func (tm *TelemetryMiddleware) EndTracingSpansInterceptor() grpc.UnaryServerInterceptor {
return func(
ctx context.Context,
req any,
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (any, error) {
resp, err := handler(ctx, req)

go func() {
trace.SpanFromContext(ctx).End()
}()

return resp, err
}
}

func (tm *TelemetryMiddleware) collectMetrics(c *fiber.Ctx) error {
func (tm *TelemetryMiddleware) collectMetrics(ctx context.Context) error {
cpuGauge, err := otel.Meter(tm.ServiceName).Int64Gauge("system.cpu.usage", metric.WithUnit("percentage"))
if err != nil {
return err
Expand All @@ -60,8 +120,8 @@ func (tm *TelemetryMiddleware) collectMetrics(c *fiber.Ctx) error {
cpuUsage := common.GetCPUUsage()
memUsage := common.GetMemUsage()

cpuGauge.Record(c.Context(), cpuUsage)
memGauge.Record(c.Context(), memUsage)
cpuGauge.Record(ctx, cpuUsage)
memGauge.Record(ctx, memUsage)

return nil
}
8 changes: 8 additions & 0 deletions common/stringUtils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package common

import (
"bytes"
"regexp"
"strings"
"unicode"

Expand Down Expand Up @@ -160,3 +161,10 @@ func RemoveChars(str string, chars map[string]bool) string {

return s
}

// ReplaceUUIDWithPlaceholder replaces UUIDs with a placeholder in a given path string.
func ReplaceUUIDWithPlaceholder(path string) string {
re := regexp.MustCompile(`[0-9a-fA-F-]{36}`)

return re.ReplaceAllString(path, ":id")
}
4 changes: 2 additions & 2 deletions components/infra/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ services:
condition: service_healthy

otel-lgtm:
container_name: otel-lgtm
container_name: midaz-otel-lgtm
image: grafana/otel-lgtm:latest
env_file:
- .env
Expand All @@ -115,7 +115,7 @@ services:
- infra_network

otel-collector:
container_name: otel-collector
container_name: midaz-otel-collector
image: otel/opentelemetry-collector-contrib
env_file:
- .env
Expand Down
2 changes: 1 addition & 1 deletion components/infra/otelcol/otel-collector-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ processors:
exporters:
debug:
otlp:
endpoint: otel-lgtm:${env:OTEL_LGTM_RECEIVER_PORT}
endpoint: midaz-otel-lgtm:${env:OTEL_LGTM_RECEIVER_PORT}
tls:
insecure: true

Expand Down
Loading

0 comments on commit 4a67e71

Please sign in to comment.