diff --git a/.env b/.env index a131cf5..27afbea 100644 --- a/.env +++ b/.env @@ -4,13 +4,13 @@ APP_KEY=ABCDEFGHIJKLMNOPQRSTUVWXYZ123456 APP_DEBUG=true APP_URL=http://localhost APP_HOST=127.0.0.1 -APP_PORT=3000 +APP_PORT=8080 GRPC_HOST=127.0.0.1 -GRPC_PORT=3001 +GRPC_PORT=3002 GRPC_USER_HOST=127.0.0.1 -GRPC_USER_PORT=3001 +GRPC_USER_PORT=3002 JWT_SECRET=ABCDEFGHIJKLMNOPQRSTUVWXYZ123456 diff --git a/.env.example b/.env.example index ac871a9..78c42f8 100644 --- a/.env.example +++ b/.env.example @@ -4,11 +4,14 @@ APP_KEY= APP_DEBUG=true APP_URL=http://localhost APP_HOST=127.0.0.1 -APP_PORT=3000 +APP_PORT=8080 GRPC_HOST= GRPC_PORT= +GRPC_USER_HOST= +GRPC_USER_PORT= + JWT_SECRET= LOG_CHANNEL=stack diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9a57bf8..6351ffe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: ubuntu: strategy: matrix: - go: [ "1.23", "1.24" ] + go: [ "1.25", "1.26" ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -44,4 +44,4 @@ jobs: - name: Install dependencies run: go mod tidy - name: Run tests - run: go test -timeout 5m -v ./... + run: go test -timeout 10m -v ./... diff --git a/app/facades/telemetry.go b/app/facades/telemetry.go new file mode 100755 index 0000000..78dba26 --- /dev/null +++ b/app/facades/telemetry.go @@ -0,0 +1,9 @@ +package facades + +import ( + "github.com/goravel/framework/contracts/telemetry" +) + +func Telemetry() telemetry.Telemetry { + return App().MakeTelemetry() +} diff --git a/app/http/controllers/grpc_controller.go b/app/http/controllers/grpc_controller.go index 745ca4f..0241f15 100644 --- a/app/http/controllers/grpc_controller.go +++ b/app/http/controllers/grpc_controller.go @@ -5,6 +5,8 @@ import ( proto "github.com/goravel/example-proto" "github.com/goravel/framework/contracts/http" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" "goravel/app/facades" ) @@ -18,6 +20,7 @@ https://github.com/goravel/example/blob/master/app/grpc/controllers/user_control type GrpcController struct { userService proto.UserServiceClient + counter metric.Int64Counter } func NewGrpcController() *GrpcController { @@ -27,8 +30,19 @@ func NewGrpcController() *GrpcController { facades.Log().Error(fmt.Sprintf("failed to connect to user server: %+v", err)) } + // We use an Int64Counter for counting discrete error events + meter := facades.Telemetry().Meter("grpc_controller") + counter, err := meter.Int64Counter( + "grpc_controller_total", + metric.WithDescription("Total number of gRPC controller requests"), + ) + if err != nil { + facades.Log().Error(fmt.Sprintf("failed to create error counter: %+v", err)) + } + return &GrpcController{ userService: proto.NewUserServiceClient(client), + counter: counter, } } @@ -43,5 +57,9 @@ func (r *GrpcController) User(ctx http.Context) http.Response { return ctx.Response().String(http.StatusInternalServerError, fmt.Sprintf("user service returns error, code: %d, message: %s", resp.Code, resp.Message)) } + r.counter.Add(ctx, 1, metric.WithAttributes( + attribute.String("method", "GrpcController/User"), + )) + return ctx.Response().Success().Json(resp.GetData()) } diff --git a/app/http/controllers/user_controller.go b/app/http/controllers/user_controller.go index 5bd27ec..1d7e60d 100644 --- a/app/http/controllers/user_controller.go +++ b/app/http/controllers/user_controller.go @@ -8,14 +8,10 @@ import ( "goravel/app/models" ) -type UserController struct { - //Dependent services -} +type UserController struct{} func NewUserController() *UserController { - return &UserController{ - //Inject services - } + return &UserController{} } func (r *UserController) Index(ctx http.Context) http.Response { diff --git a/bootstrap/app.go b/bootstrap/app.go index 6ea51ac..c60accf 100644 --- a/bootstrap/app.go +++ b/bootstrap/app.go @@ -12,6 +12,8 @@ import ( "github.com/goravel/framework/http/limit" httpmiddleware "github.com/goravel/framework/http/middleware" "github.com/goravel/framework/session/middleware" + telemetrygrpc "github.com/goravel/framework/telemetry/instrumentation/grpc" + telemetryhttp "github.com/goravel/framework/telemetry/instrumentation/http" "google.golang.org/grpc" "google.golang.org/grpc/stats" @@ -50,6 +52,7 @@ func Boot() contractsfoundation.Application { handler.Append( httpmiddleware.Throttle("global"), middleware.StartSession(), + telemetryhttp.Telemetry(), ).Recover(func(ctx http.Context, err any) { facades.Log().Error(err) _ = ctx.Response().String(http.StatusInternalServerError, "recover").Abort() @@ -68,10 +71,16 @@ func Boot() contractsfoundation.Application { } }). WithGrpcServerStatsHandlers(func() []stats.Handler { - return []stats.Handler{} + return []stats.Handler{ + telemetrygrpc.NewServerStatsHandler(), + } }). WithGrpcClientStatsHandlers(func() map[string][]stats.Handler { - return map[string][]stats.Handler{} + return map[string][]stats.Handler{ + "default": { + telemetrygrpc.NewClientStatsHandler(), + }, + } }). WithPaths(func(paths configuration.Paths) { paths.App("app") diff --git a/bootstrap/providers.go b/bootstrap/providers.go index 2cc1078..13f2f3f 100644 --- a/bootstrap/providers.go +++ b/bootstrap/providers.go @@ -20,6 +20,7 @@ import ( "github.com/goravel/framework/route" "github.com/goravel/framework/schedule" "github.com/goravel/framework/session" + "github.com/goravel/framework/telemetry" "github.com/goravel/framework/testing" "github.com/goravel/framework/translation" "github.com/goravel/framework/validation" @@ -68,5 +69,6 @@ func Providers() []foundation.ServiceProvider { &redis.ServiceProvider{}, &gin.ServiceProvider{}, &fiber.ServiceProvider{}, + &telemetry.ServiceProvider{}, } } diff --git a/config/grpc.go b/config/grpc.go index dacaf49..466e274 100644 --- a/config/grpc.go +++ b/config/grpc.go @@ -20,7 +20,7 @@ func init() { "port": config.Env("GRPC_USER_PORT"), // the group name of UnaryClientInterceptorGroups "interceptors": []string{}, - "stats_handlers": []string{}, + "stats_handlers": []string{"default"}, }, }, }) diff --git a/config/http.go b/config/http.go index 9652b1e..8a548f1 100644 --- a/config/http.go +++ b/config/http.go @@ -75,12 +75,13 @@ func init() { "default_client": config.Env("HTTP_CLIENT_DEFAULT", "default"), "clients": map[string]any{ "default": map[string]any{ - "base_url": config.Env("HTTP_CLIENT_BASE_URL", "http://127.0.0.1:3000"), + "base_url": config.Env("HTTP_CLIENT_BASE_URL", "http://127.0.0.1:8080"), "timeout": config.Env("HTTP_CLIENT_TIMEOUT", "30s"), "max_idle_conns": config.Env("HTTP_CLIENT_MAX_IDLE_CONNS", 100), "max_idle_conns_per_host": config.Env("HTTP_CLIENT_MAX_IDLE_CONNS_PER_HOST", 2), "max_conns_per_host": config.Env("HTTP_CLIENT_MAX_CONN_PER_HOST", 0), "idle_conn_timeout": config.Env("HTTP_CLIENT_IDLE_CONN_TIMEOUT", "90s"), + "enable_telemetry": true, }, }, }) diff --git a/config/logging.go b/config/logging.go index d65e048..5d35f34 100644 --- a/config/logging.go +++ b/config/logging.go @@ -39,6 +39,10 @@ func init() { "print": true, "formatter": "text", }, + "otel": map[string]any{ + "driver": "otel", + "instrument_name": config.GetString("APP_NAME", "goravel/log"), + }, }, }) } diff --git a/config/telemetry.go b/config/telemetry.go index ab056a2..1c5681e 100755 --- a/config/telemetry.go +++ b/config/telemetry.go @@ -67,7 +67,7 @@ func init() { // The ratio for "traceidratio" sampling (0.0 to 1.0). // e.g., 0.1 records ~10% of traces. - "ratio": config.Env("OTEL_TRACES_SAMPLER_RATIO", 0.05), + "ratio": config.Env("OTEL_TRACES_SAMPLER_RATIO", 1.0), }, }, @@ -89,11 +89,11 @@ func init() { "reader": map[string]any{ // Interval: How often metrics are pushed. // Format: Duration string (e.g., "60s", "1m", "500ms"). - "interval": config.Env("OTEL_METRIC_EXPORT_INTERVAL", "60s"), + "interval": config.Env("OTEL_METRIC_EXPORT_INTERVAL", "1s"), // Timeout: Max time allowed for export before cancelling. // Format: Duration string (e.g., "30s", "10s"). - "timeout": config.Env("OTEL_METRIC_EXPORT_TIMEOUT", "30s"), + "timeout": config.Env("OTEL_METRIC_EXPORT_TIMEOUT", "10s"), }, }, @@ -118,7 +118,57 @@ func init() { // Timeout: Max time allowed for export before cancelling. // Format: Duration string (e.g., "30s"). - "timeout": config.Env("OTEL_LOG_EXPORT_TIMEOUT", "30s"), + "timeout": config.Env("OTEL_LOG_EXPORT_TIMEOUT", "10s"), + }, + }, + + // Instrumentation Configuration + // + // Configures the automatic instrumentation for specific components. + "instrumentation": map[string]any{ + // HTTP Server Instrumentation + // + // Configures the telemetry middleware for incoming HTTP requests. + "http_server": map[string]any{ + "enabled": config.Env("OTEL_HTTP_SERVER_ENABLED", true), + "excluded_paths": []string{}, // e.g., ["/health", "/metrics"] + "excluded_methods": []string{}, // e.g., ["OPTIONS", "HEAD"] + }, + + // HTTP Client Instrumentation + // + // Configures instrumentation for outgoing HTTP requests made through the + // application's HTTP client facade. This acts as a global kill switch for + // HTTP client telemetry across all clients. + // + // To disable telemetry for a specific client, set + // "http.clients.{client_name}.enable_telemetry" to false for the + // corresponding client configuration. + "http_client": map[string]any{ + "enabled": config.Env("OTEL_HTTP_CLIENT_ENABLED", true), + }, + + // gRPC Server Instrumentation + // + // Configures the instrumentation for incoming gRPC requests to your server. + "grpc_server": map[string]any{ + "enabled": config.Env("OTEL_GRPC_SERVER_ENABLED", true), + }, + + // gRPC Client Instrumentation + // + // Configures the instrumentation for outgoing gRPC calls made by your application. + "grpc_client": map[string]any{ + "enabled": config.Env("OTEL_GRPC_CLIENT_ENABLED", true), + }, + + // Log Instrumentation + // + // Configures the instrumentation for the application logger. + // Disabling this acts as a global kill switch for sending logs to the OTel exporter, + // which can be useful for reducing cost/noise without changing logging config. + "log": map[string]any{ + "enabled": config.Env("OTEL_LOG_ENABLED", true), }, }, @@ -127,7 +177,7 @@ func init() { // Defines the details for connecting to external telemetry backends. // These definitions are referenced by name in the signal sections above. // - // Supported drivers: "otlp", "zipkin", "console", "custom" + // Supported drivers: "otlp", "console", "custom" "exporters": map[string]any{ // OTLP Trace Exporter // Reference: https://opentelemetry.io/docs/specs/otel/protocol/ @@ -150,7 +200,11 @@ func init() { "otlpmetric": map[string]any{ "driver": "otlp", "endpoint": config.Env("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", "http://localhost:4318"), + + // Protocol: "http/protobuf", "http/json" or "grpc". "protocol": config.Env("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", "http/protobuf"), + + // Set to false to require TLS/SSL. "insecure": config.Env("OTEL_EXPORTER_OTLP_METRICS_INSECURE", true), // Timeout: Max time to wait for the backend to acknowledge. @@ -167,7 +221,11 @@ func init() { "otlplog": map[string]any{ "driver": "otlp", "endpoint": config.Env("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", "http://localhost:4318"), + + // Protocol: "http/protobuf", "http/json" or "grpc". "protocol": config.Env("OTEL_EXPORTER_OTLP_LOGS_PROTOCOL", "http/protobuf"), + + // Set to false to require TLS/SSL. "insecure": config.Env("OTEL_EXPORTER_OTLP_LOGS_INSECURE", true), // Timeout: Max time to wait for the backend to acknowledge. @@ -175,16 +233,26 @@ func init() { "timeout": config.Env("OTEL_EXPORTER_OTLP_LOGS_TIMEOUT", "10s"), }, - // Zipkin Trace Exporter (Tracing only) - "zipkin": map[string]any{ - "driver": "zipkin", - "endpoint": config.Env("OTEL_EXPORTER_ZIPKIN_ENDPOINT", "http://localhost:9411/api/v2/spans"), - }, - // Console Exporter (Debugging) // Prints telemetry data to stdout. "console": map[string]any{ "driver": "console", + + // Set to true to pretty print the output. + "pretty_print": false, + }, + + // Custom Exporter + // + // Use this to provide your own exporter implementation. + // The "via" key should contain an instance of your custom exporter. + "custom": map[string]any{ + "driver": "custom", + + // For traces, via should be an instance of go.opentelemetry.io/otel/sdk/trace.SpanExporter or func(context.Context) (sdktrace.SpanExporter, error) + // For metrics, via should be an instance of go.opentelemetry.io/otel/sdk/metric.Reader or func(context.Context) (sdkmetric.Reader, error) + // For log, via should be an instance of go.opentelemetry.io/otel/sdk/log.Exporter or func(context.Context) (sdklog.Exporter, error) + // "via": YourCustomExporterInstance, }, }, }) diff --git a/docker-compose.yml b/docker-compose.yml index 81f36ea..a388e24 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,49 @@ -version: '3' +version: '3.8' services: - goravel: - build: - context: . + otel-collector: + image: otel/opentelemetry-collector-contrib:latest + restart: always + command: ["--config=/etc/otel-config.yaml"] + volumes: + - ./otel-config.yaml:/etc/otel-config.yaml + ports: + - "4317:4317" + - "4318:4318" + + jaeger: + image: jaegertracing/all-in-one:latest + restart: always + ports: + - "16686:16686" + + prometheus: + image: prom/prometheus:latest + restart: always + volumes: + - ./prometheus.yaml:/etc/prometheus/prometheus.yml + command: + - --config.file=/etc/prometheus/prometheus.yml + - --web.enable-remote-write-receiver + ports: + - "9090:9090" + + loki: + image: grafana/loki:latest + restart: always + volumes: + - ./loki.yaml:/etc/loki/local-config.yaml + command: -config.file=/etc/loki/local-config.yaml ports: - - "3000:3000" + - "3100:3100" + + grafana: + image: grafana/grafana:latest restart: always + ports: + - "3001:3000" + environment: + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin + volumes: + - ./grafana-datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml \ No newline at end of file diff --git a/go.mod b/go.mod index 4619494..a6df035 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module goravel -go 1.24.0 +go 1.25.0 require ( github.com/99designs/gqlgen v0.17.57 @@ -10,7 +10,7 @@ require ( github.com/goravel/cos v1.17.0 github.com/goravel/example-proto v0.0.1 github.com/goravel/fiber v1.17.0 - github.com/goravel/framework v1.17.1-0.20260209063303-182131ae113a + github.com/goravel/framework v1.17.2-0.20260215045043-365219d1eb4e github.com/goravel/gin v1.17.0 github.com/goravel/minio v1.17.0 github.com/goravel/mysql v1.17.0 @@ -67,7 +67,7 @@ require ( github.com/charmbracelet/bubbletea v1.3.10 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect github.com/charmbracelet/huh v0.8.0 // indirect - github.com/charmbracelet/huh/spinner v0.0.0-20251215014908-6f7d32faaff3 // indirect + github.com/charmbracelet/huh/spinner v0.0.0-20260209112015-5c5971ef3aeb // indirect github.com/charmbracelet/lipgloss v1.1.0 // indirect github.com/charmbracelet/x/ansi v0.10.1 // indirect github.com/charmbracelet/x/cellbuf v0.0.13 // indirect @@ -83,6 +83,7 @@ require ( github.com/dromara/carbon/v2 v2.6.11 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect @@ -113,8 +114,7 @@ require ( github.com/gookit/filter v1.2.3 // indirect github.com/gookit/goutil v0.7.1 // indirect github.com/gookit/validate v1.5.6 // indirect - github.com/goravel/file-rotatelogs/v2 v2.4.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -152,7 +152,6 @@ require ( github.com/ncruces/go-sqlite3 v0.25.0 // indirect github.com/ncruces/go-sqlite3/gormlite v0.24.0 // indirect github.com/ncruces/julianday v1.0.0 // indirect - github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/philhofer/fwd v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -167,8 +166,8 @@ require ( github.com/rs/xid v1.6.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/samber/lo v1.52.0 // indirect - github.com/samber/slog-common v0.19.0 // indirect - github.com/samber/slog-multi v1.7.0 // indirect + github.com/samber/slog-common v0.20.0 // indirect + github.com/samber/slog-multi v1.7.1 // indirect github.com/sosodev/duration v1.3.1 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect @@ -190,42 +189,43 @@ require ( github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect go.opentelemetry.io/contrib/propagators/b3 v1.39.0 // indirect - go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel v1.40.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/zipkin v1.39.0 // indirect go.opentelemetry.io/otel/log v0.15.0 // indirect - go.opentelemetry.io/otel/metric v1.39.0 // indirect - go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/sdk v1.40.0 // indirect go.opentelemetry.io/otel/sdk/log v0.15.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect - go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect + go.opentelemetry.io/otel/trace v1.40.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/mock v0.5.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.47.0 // indirect + golang.org/x/crypto v0.48.0 // indirect golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/net v0.49.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/term v0.39.0 // indirect - golang.org/x/text v0.33.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/term v0.40.0 // indirect + golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.41.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect + golang.org/x/tools v0.42.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.6.0 // indirect diff --git a/go.sum b/go.sum index 427c1c0..a55c26c 100644 --- a/go.sum +++ b/go.sum @@ -125,8 +125,8 @@ github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4p github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= github.com/charmbracelet/huh v0.8.0 h1:Xz/Pm2h64cXQZn/Jvele4J3r7DDiqFCNIVteYukxDvY= github.com/charmbracelet/huh v0.8.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4= -github.com/charmbracelet/huh/spinner v0.0.0-20251215014908-6f7d32faaff3 h1:KUeWGoKnmyrLaDIa0smE6pK5eFMZWNIxPGweQR12iLg= -github.com/charmbracelet/huh/spinner v0.0.0-20251215014908-6f7d32faaff3/go.mod h1:OMqKat/mm9a/qOnpuNOPyYO9bPzRNnmzLnRZT5KYltg= +github.com/charmbracelet/huh/spinner v0.0.0-20260209112015-5c5971ef3aeb h1:loPLebLFAjTolD/IwvNJLEMX+Nq2elBMwtzpueW014Y= +github.com/charmbracelet/huh/spinner v0.0.0-20260209112015-5c5971ef3aeb/go.mod h1:OMqKat/mm9a/qOnpuNOPyYO9bPzRNnmzLnRZT5KYltg= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= @@ -179,6 +179,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= @@ -277,10 +279,10 @@ github.com/goravel/example-proto v0.0.1 h1:ZxETeKREQWjuJ49bX/Hqj1NLR5Vyj489Ks6dR github.com/goravel/example-proto v0.0.1/go.mod h1:I8IPsHr4Ndf7KxmdsRpBR2LQ0Geo48+pjv9IIWf3mZg= github.com/goravel/fiber v1.17.0 h1:XMkuz29hJzaN5mW7dK70oc6FfMDUQeYPbrLyBQoiIA8= github.com/goravel/fiber v1.17.0/go.mod h1:hu2eLwQ6u8ZDFsVWHeV1q0bh7g7PRQg0VZxceVr29Uc= -github.com/goravel/file-rotatelogs/v2 v2.4.2 h1:g68AzbePXcm0V2CpUMc9j4qVzcDn7+7aoWSjZ51C0m4= -github.com/goravel/file-rotatelogs/v2 v2.4.2/go.mod h1:23VuSW8cBS4ax5cmbV+5AaiLpq25b8UJ96IhbAkdo8I= -github.com/goravel/framework v1.17.1-0.20260209063303-182131ae113a h1:I8JRIL12diRNpRjx2mggtvVeOVmwMXQd9jnaWG8CSQI= -github.com/goravel/framework v1.17.1-0.20260209063303-182131ae113a/go.mod h1:ClgXBsig8R2w+xAJT2TVxpAkazGHFtvVmNBolYT94gQ= +github.com/goravel/framework v1.17.2-0.20260214094639-2ce411599275 h1:3scg5HXi4a+mj/jb9EHUzvCkmiZz6toA+rWxv0LkIlk= +github.com/goravel/framework v1.17.2-0.20260214094639-2ce411599275/go.mod h1:bcEQ1Cvo2G9bM6aWWLYa6IiQvpamDe/7sP8FitIXEM8= +github.com/goravel/framework v1.17.2-0.20260215045043-365219d1eb4e h1:pUys/0n2jtlSvFVxTxeg/aWwIkY53sixMps6AWrdpD0= +github.com/goravel/framework v1.17.2-0.20260215045043-365219d1eb4e/go.mod h1:bcEQ1Cvo2G9bM6aWWLYa6IiQvpamDe/7sP8FitIXEM8= github.com/goravel/gin v1.17.0 h1:8H66v9GaYJR9UQ7C0VOef25/r8t/BAH9ZxlvxbHprlc= github.com/goravel/gin v1.17.0/go.mod h1:n0W6V/H+E0mqO+Gh+UMjeBANZe//lpWJ6X7kF3kwxR8= github.com/goravel/minio v1.17.0 h1:WGiPP/KZl/fuDpT9THRM83wjhLCqe1oIAyNVJvVjhS4= @@ -303,8 +305,8 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -360,8 +362,6 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhR github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= -github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= @@ -420,8 +420,6 @@ github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvz github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= -github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= @@ -468,10 +466,10 @@ github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDc github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= -github.com/samber/slog-common v0.19.0 h1:fNcZb8B2uOLooeYwFpAlKjkQTUafdjfqKcwcC89G9YI= -github.com/samber/slog-common v0.19.0/go.mod h1:dTz+YOU76aH007YUU0DffsXNsGFQRQllPQh9XyNoA3M= -github.com/samber/slog-multi v1.7.0 h1:GKhbkxU3ujkyMsefkuz4qvE6EcgtSuqjFisPnfdzVLI= -github.com/samber/slog-multi v1.7.0/go.mod h1:qTqzmKdPpT0h4PFsTN5rYRgLwom1v+fNGuIrl1Xnnts= +github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc= +github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8= +github.com/samber/slog-multi v1.7.1 h1:aCLXHRxgU+2v0PVlEOh7phynzM7CRo89ZgFtOwaqVEE= +github.com/samber/slog-multi v1.7.1/go.mod h1:A4KQC99deqfkCDJcL/cO3kX6McX7FffQAx/8QHink+c= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= @@ -549,10 +547,14 @@ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3i github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/contrib/propagators/b3 v1.39.0 h1:PI7pt9pkSnimWcp5sQhUA9OzLbc3Ba4sL+VEUTNsxrk= go.opentelemetry.io/contrib/propagators/b3 v1.39.0/go.mod h1:5gV/EzPnfYIwjzj+6y8tbGW2PKWhcsz5e/7twptRVQY= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= +go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 h1:W+m0g+/6v3pa5PgVf2xoFMi5YtNR06WtS7ve5pcvLtM= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0/go.mod h1:JM31r0GGZ/GU94mX8hN4D8v6e40aFlUECSQ48HaLgHM= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 h1:EKpiGphOYq3CYnIe2eX9ftUkyU+Y8Dtte8OaWyHJ4+I= @@ -561,8 +563,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 h1:nKP4Z2ejtHn3yShBb+2KawiXgpn8In5cT7aO2wXuOTE= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0/go.mod h1:NwjeBbNigsO4Aj9WgM0C+cKIrxsZUaRmZUO7A8I7u8o= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= @@ -573,24 +575,22 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= -go.opentelemetry.io/otel/exporters/zipkin v1.39.0 h1:zas8I6MeDWD5rxJmkXcCPRnpvNtZHkENiTkX/eJlycg= -go.opentelemetry.io/otel/exporters/zipkin v1.39.0/go.mod h1:SmFF1H2pTNFFvD4NqRanxPP8W+8KjTgFJhJQi3C6Co0= go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY= go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4= go.opentelemetry.io/otel/log/logtest v0.15.0 h1:porNFuxAjodl6LhePevOc3n7bo3Wi3JhGXNWe7KP8iU= go.opentelemetry.io/otel/log/logtest v0.15.0/go.mod h1:c8epqBXGHgS1LiNgmD+LuNYK9lSS3mqvtMdxLsfJgLg= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= +go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE= go.opentelemetry.io/otel/sdk/log v0.15.0/go.mod h1:qDC/FlKQCXfH5hokGsNg9aUBGMJQsrUyeOiW5u+dKBQ= go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= +go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -616,8 +616,8 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -636,8 +636,8 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -656,8 +656,8 @@ golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -693,8 +693,8 @@ golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -712,8 +712,8 @@ golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -727,8 +727,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -740,8 +740,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -751,10 +751,10 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/grafana-datasources.yaml b/grafana-datasources.yaml new file mode 100644 index 0000000..e1b7a31 --- /dev/null +++ b/grafana-datasources.yaml @@ -0,0 +1,18 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + + - name: Loki + type: loki + access: proxy + url: http://loki:3100 + + - name: Jaeger + type: jaeger + access: proxy + url: http://jaeger:16686 \ No newline at end of file diff --git a/loki.yaml b/loki.yaml new file mode 100644 index 0000000..e537f1f --- /dev/null +++ b/loki.yaml @@ -0,0 +1,32 @@ +auth_enabled: false + +server: + http_listen_port: 3100 + grpc_listen_port: 9096 + +common: + instance_addr: 127.0.0.1 + path_prefix: /tmp/loki + storage: + filesystem: + chunks_directory: /tmp/loki/chunks + rules_directory: /tmp/loki/rules + replication_factor: 1 + ring: + kvstore: + store: inmemory + +schema_config: + configs: + - from: 2020-10-24 + store: tsdb + object_store: filesystem + schema: v13 + index: + prefix: index_ + period: 24h + +limits_config: + allow_structured_metadata: true + reject_old_samples: true + reject_old_samples_max_age: 168h \ No newline at end of file diff --git a/otel-config.yaml b/otel-config.yaml new file mode 100644 index 0000000..aefb96c --- /dev/null +++ b/otel-config.yaml @@ -0,0 +1,41 @@ +receivers: + otlp: + protocols: + http: + endpoint: 0.0.0.0:4318 + grpc: + endpoint: 0.0.0.0:4317 + +processors: + batch: + +exporters: + otlp/jaeger: + endpoint: "jaeger:4317" + tls: + insecure: true + + prometheusremotewrite: + endpoint: "http://prometheus:9090/api/v1/write" + resource_to_telemetry_conversion: + enabled: true + + otlphttp/loki: + endpoint: "http://loki:3100/otlp" + tls: + insecure: true + +service: + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [otlp/jaeger] + metrics: + receivers: [otlp] + processors: [batch] + exporters: [prometheusremotewrite] + logs: + receivers: [otlp] + processors: [batch] + exporters: [otlphttp/loki] \ No newline at end of file diff --git a/prometheus.yaml b/prometheus.yaml new file mode 100644 index 0000000..91d2a5b --- /dev/null +++ b/prometheus.yaml @@ -0,0 +1,15 @@ +global: + scrape_interval: 15s + +scrape_configs: + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] + + - job_name: 'otel-collector' + static_configs: + - targets: ['otel-collector:8888'] + + - job_name: 'loki' + static_configs: + - targets: ['loki:3100'] \ No newline at end of file diff --git a/routes/api.go b/routes/api.go index c02396c..8f091f6 100644 --- a/routes/api.go +++ b/routes/api.go @@ -62,7 +62,7 @@ func Api() { return err } - time.Sleep(1 * time.Second) + time.Sleep(100 * time.Millisecond) } return nil @@ -151,4 +151,24 @@ func Api() { }) }).Name("url.post") }) + + facades.Route().Get("telemetry", func(ctx http.Context) http.Response { + facades.Log().Channel("otel").WithContext(ctx).Info("test telemetry log") + + resp, err := facades.Http().WithContext(ctx).Get("/grpc/user?token=1") + if err != nil { + return ctx.Response().Json(http.StatusInternalServerError, http.Json{ + "error": err.Error(), + }) + } + + body, err := resp.Body() + if err != nil { + return ctx.Response().Json(http.StatusInternalServerError, http.Json{ + "error": err.Error(), + }) + } + + return ctx.Response().Success().String(body) + }) } diff --git a/tests/feature/telemetry_test.go b/tests/feature/telemetry_test.go new file mode 100644 index 0000000..0e6dd4e --- /dev/null +++ b/tests/feature/telemetry_test.go @@ -0,0 +1,86 @@ +package feature + +import ( + "fmt" + "testing" + "time" + + "github.com/goravel/framework/facades" + "github.com/stretchr/testify/suite" + + "goravel/tests" +) + +type TelemetryTestSuite struct { + suite.Suite + tests.TestCase +} + +func TestTelemetryTestSuite(t *testing.T) { + suite.Run(t, &TelemetryTestSuite{}) +} + +func (s *TelemetryTestSuite) SetupSuite() { + s.False(facades.Process().Path("../../").Run("docker compose up -d prometheus jaeger loki otel-collector").Failed()) + time.Sleep(5 * time.Second) + + resp, err := s.Http(s.T()).Get("/telemetry") + s.Require().NoError(err) + resp.AssertSuccessful() + + // Wait for telemetry data to be exported + time.Sleep(2 * time.Second) +} + +func (s *TelemetryTestSuite) TearDownSuite() { + s.False(facades.Process().Path("../../").Run("docker compose down").Failed()) +} + +func (s *TelemetryTestSuite) TestTraces() { + appName := facades.Config().GetString("app.name") + resp, err := facades.Http().Get("http://localhost:16686/api/traces?service=" + appName) + s.NoError(err) + + body, err := resp.Body() + s.NoError(err) + + s.Contains(body, "GET /telemetry") + s.Contains(body, "HTTP GET") + s.Contains(body, "user.UserService/GetUser") + s.Contains(body, "GET /grpc/user") +} + +func (s *TelemetryTestSuite) TestMetrics() { + resp, err := facades.Http().Get("http://localhost:9090/api/v1/query?query=grpc_controller_total") + s.NoError(err) + + body, err := resp.Body() + s.NoError(err) + + s.Contains(body, "grpc_controller_total") + s.Contains(body, "GrpcController/User") +} + +func (s *TelemetryTestSuite) TestLogs() { + appName := facades.Config().GetString("app.name") + + // Calculate time range (last 5 minutes to now) + end := time.Now().UnixNano() + start := time.Now().Add(-5 * time.Minute).UnixNano() + + // LogQL query to search for logs from our service + query := `{service_name="` + appName + `"}` + + // Query Loki using range query API + url := fmt.Sprintf("http://localhost:3100/loki/api/v1/query_range?query=%s&start=%d&end=%d", + query, start, end) + + resp, err := facades.Http().Get(url) + s.NoError(err) + + body, err := resp.Body() + s.NoError(err) + + // Verify logs contain expected content + s.Contains(body, "test telemetry log") +}