Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 ./...
9 changes: 9 additions & 0 deletions app/facades/telemetry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package facades

import (
"github.com/goravel/framework/contracts/telemetry"
)

func Telemetry() telemetry.Telemetry {
return App().MakeTelemetry()
}
18 changes: 18 additions & 0 deletions app/http/controllers/grpc_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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 {
Expand All @@ -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,
}
}

Expand All @@ -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())
}
8 changes: 2 additions & 6 deletions app/http/controllers/user_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
13 changes: 11 additions & 2 deletions bootstrap/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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()
Expand All @@ -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")
Expand Down
2 changes: 2 additions & 0 deletions bootstrap/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -68,5 +69,6 @@ func Providers() []foundation.ServiceProvider {
&redis.ServiceProvider{},
&gin.ServiceProvider{},
&fiber.ServiceProvider{},
&telemetry.ServiceProvider{},
}
}
2 changes: 1 addition & 1 deletion config/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
},
},
})
Expand Down
3 changes: 2 additions & 1 deletion config/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
})
Expand Down
4 changes: 4 additions & 0 deletions config/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ func init() {
"print": true,
"formatter": "text",
},
"otel": map[string]any{
"driver": "otel",
"instrument_name": config.GetString("APP_NAME", "goravel/log"),
},
},
})
}
90 changes: 79 additions & 11 deletions config/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
},
},

Expand All @@ -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"),
},
},

Expand All @@ -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),
},
},

Expand All @@ -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/
Expand All @@ -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.
Expand All @@ -167,24 +221,38 @@ 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.
// Format: Duration string (e.g., "10s", "500ms").
"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,
},
},
})
Expand Down
Loading