mirror of
https://gitee.com/johng/gf
synced 2026-06-12 04:03:22 +08:00
Compare commits
10 Commits
contrib/dr
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
| ef7078f85f | |||
| ba6c629a4a | |||
| 7b67ce4c68 | |||
| bc38eb8d72 | |||
| cb66fc0cc2 | |||
| cb1d274074 | |||
| 5ea3c4ace6 | |||
| aa44a07d9e | |||
| 16ed1f8b2e | |||
| caf53edebc |
268
OTEL_V2.8_EXAMPLE.md
Normal file
268
OTEL_V2.8_EXAMPLE.md
Normal file
@ -0,0 +1,268 @@
|
||||
# OpenTelemetry V2.8 Improvements Example
|
||||
|
||||
This example demonstrates the new configurable OpenTelemetry tracing features for SQL, HTTP requests, and HTTP responses.
|
||||
|
||||
**Updated to OpenTelemetry v1.38.0 with Independent OTEL Parameters**
|
||||
|
||||
## HTTP Server Configuration
|
||||
|
||||
### New Independent OTEL Configuration (Recommended)
|
||||
```yaml
|
||||
server:
|
||||
address: ":8080"
|
||||
otel:
|
||||
traceRequestEnabled: true # Enable HTTP request parameter tracing
|
||||
traceResponseEnabled: true # Enable HTTP response body tracing
|
||||
```
|
||||
|
||||
### Legacy Configuration (Still Supported)
|
||||
```yaml
|
||||
server:
|
||||
address: ":8080"
|
||||
otelTraceRequestEnabled: true # Enable HTTP request parameter tracing
|
||||
otelTraceResponseEnabled: true # Enable HTTP response body tracing
|
||||
```
|
||||
|
||||
## Database Configuration
|
||||
|
||||
### New Independent OTEL Configuration (Recommended)
|
||||
```yaml
|
||||
database:
|
||||
default:
|
||||
type: "mysql"
|
||||
host: "127.0.0.1"
|
||||
port: "3306"
|
||||
user: "your_user"
|
||||
pass: "your_password"
|
||||
name: "your_database"
|
||||
otel:
|
||||
traceSQLEnabled: true # Enable SQL statement tracing
|
||||
```
|
||||
|
||||
### Legacy Configuration (Still Supported)
|
||||
```yaml
|
||||
database:
|
||||
default:
|
||||
type: "mysql"
|
||||
host: "127.0.0.1"
|
||||
port: "3306"
|
||||
user: "your_user"
|
||||
pass: "your_password"
|
||||
name: "your_database"
|
||||
otelTraceSQLEnabled: true # Enable SQL statement tracing
|
||||
```
|
||||
|
||||
## Programmatic Configuration
|
||||
|
||||
### HTTP Server - New Independent OTEL Configuration
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/internal/otel"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
|
||||
// Configure using new independent OTEL configuration
|
||||
config := ghttp.NewConfig()
|
||||
config.Address = ":8080"
|
||||
config.Otel = otel.Config{
|
||||
TraceRequestEnabled: true,
|
||||
TraceResponseEnabled: true,
|
||||
}
|
||||
s.SetConfig(config)
|
||||
|
||||
s.BindHandler("/api/test", func(r *ghttp.Request) {
|
||||
// This handler will have its request parameters and response traced
|
||||
r.Response.WriteJson(g.Map{
|
||||
"message": "Hello World",
|
||||
"input": r.Get("input"),
|
||||
})
|
||||
})
|
||||
|
||||
s.Run()
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP Server - Legacy Configuration (Still Supported)
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
|
||||
// Enable tracing via configuration map (legacy approach)
|
||||
s.SetConfigWithMap(g.Map{
|
||||
"OtelTraceRequestEnabled": true,
|
||||
"OtelTraceResponseEnabled": true,
|
||||
})
|
||||
|
||||
s.BindHandler("/api/test", func(r *ghttp.Request) {
|
||||
// This handler will have its request parameters and response traced
|
||||
r.Response.WriteJson(g.Map{
|
||||
"message": "Hello World",
|
||||
"input": r.Get("input"),
|
||||
})
|
||||
})
|
||||
|
||||
s.Run()
|
||||
}
|
||||
```
|
||||
|
||||
### Database - New Independent OTEL Configuration
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/internal/otel"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure database with new independent OTEL configuration
|
||||
config := gdb.ConfigNode{
|
||||
Type: "mysql",
|
||||
Host: "127.0.0.1",
|
||||
Port: "3306",
|
||||
User: "your_user",
|
||||
Pass: "your_password",
|
||||
Name: "your_database",
|
||||
Otel: otel.Config{
|
||||
TraceSQLEnabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
db, err := gdb.New(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// SQL statements will now be traced
|
||||
result, err := db.Query("SELECT * FROM users WHERE id = ?", 1)
|
||||
// ... handle result
|
||||
}
|
||||
```
|
||||
|
||||
### Database - Legacy Configuration (Still Supported)
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure database with legacy OTEL configuration
|
||||
config := gdb.ConfigNode{
|
||||
Type: "mysql",
|
||||
Host: "127.0.0.1",
|
||||
Port: "3306",
|
||||
User: "your_user",
|
||||
Pass: "your_password",
|
||||
Name: "your_database",
|
||||
OtelTraceSQLEnabled: true, // Legacy field
|
||||
}
|
||||
|
||||
db, err := gdb.New(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// SQL statements will now be traced
|
||||
result, err := db.Query("SELECT * FROM users WHERE id = ?", 1)
|
||||
// ... handle result
|
||||
}
|
||||
```
|
||||
|
||||
## Trace Output Examples
|
||||
|
||||
### HTTP Method Tracing
|
||||
All HTTP requests now include the HTTP method in traces:
|
||||
- `http.method: GET`
|
||||
- `http.method: POST`
|
||||
- `http.method: PUT`
|
||||
- `http.method: DELETE`
|
||||
|
||||
### Request Parameter Tracing (when enabled)
|
||||
```json
|
||||
{
|
||||
"http.request.params": {
|
||||
"username": "john",
|
||||
"email": "john@example.com",
|
||||
"query_param": "value"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response Body Tracing (when enabled)
|
||||
```json
|
||||
{
|
||||
"http.response.body": {
|
||||
"code": 200,
|
||||
"message": "Success",
|
||||
"data": {"id": 1, "name": "John Doe"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### SQL Tracing (when enabled)
|
||||
```json
|
||||
{
|
||||
"db.execution.sql": "SELECT * FROM users WHERE id = ? AND status = ?",
|
||||
"db.execution.cost": "15 ms",
|
||||
"db.execution.rows": "1"
|
||||
}
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **OpenTelemetry v1.38.0**: Updated to the latest OpenTelemetry version with improved performance and features
|
||||
2. **Independent Configuration**: New modular OTEL configuration structure for better organization
|
||||
3. **Configurable**: All new tracing features are opt-in via configuration
|
||||
4. **Performance**: Only enabled features add overhead
|
||||
5. **Backward Compatible**: Legacy configuration fields still work alongside new structure
|
||||
6. **Comprehensive**: Covers SQL, HTTP requests, and HTTP responses
|
||||
7. **Size Aware**: Respects content size limits to prevent memory issues
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### From Legacy to New Configuration
|
||||
|
||||
#### HTTP Server
|
||||
```go
|
||||
// Legacy (still works)
|
||||
s.SetConfigWithMap(g.Map{
|
||||
"OtelTraceRequestEnabled": true,
|
||||
})
|
||||
|
||||
// New (recommended)
|
||||
config := ghttp.NewConfig()
|
||||
config.Otel.TraceRequestEnabled = true
|
||||
s.SetConfig(config)
|
||||
```
|
||||
|
||||
#### Database
|
||||
```go
|
||||
// Legacy (still works)
|
||||
config := gdb.ConfigNode{
|
||||
OtelTraceSQLEnabled: true,
|
||||
}
|
||||
|
||||
// New (recommended)
|
||||
config := gdb.ConfigNode{
|
||||
Otel: otel.Config{
|
||||
TraceSQLEnabled: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The new configuration provides better organization and allows for future OTEL features to be grouped logically while maintaining full backward compatibility.
|
||||
@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/otel"
|
||||
"github.com/gogf/gf/v2/os/gcache"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
@ -66,6 +67,15 @@ type ConfigNode struct {
|
||||
// Optional field
|
||||
Debug bool `json:"debug"`
|
||||
|
||||
// Otel specifies the OpenTelemetry tracing configuration
|
||||
// Optional field
|
||||
Otel otel.Config `json:"otel"`
|
||||
|
||||
// OtelTraceSQLEnabled enables OpenTelemetry tracing for SQL operations
|
||||
// Deprecated: Use Otel.TraceSQLEnabled instead. This field is kept for backward compatibility.
|
||||
// Optional field
|
||||
OtelTraceSQLEnabled bool `json:"otelTraceSQLEnabled"`
|
||||
|
||||
// Prefix specifies the table name prefix
|
||||
// Optional field
|
||||
Prefix string `json:"prefix"`
|
||||
@ -483,3 +493,15 @@ func parseConfigNodeLink(node *ConfigNode) (*ConfigNode, error) {
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// IsOtelTraceSQLEnabled returns whether SQL tracing is enabled for this configuration.
|
||||
// It checks both the new Otel.TraceSQLEnabled field and the deprecated OtelTraceSQLEnabled field
|
||||
// for backward compatibility.
|
||||
func (node *ConfigNode) IsOtelTraceSQLEnabled() bool {
|
||||
// Check new configuration first
|
||||
if node.Otel.TraceSQLEnabled {
|
||||
return true
|
||||
}
|
||||
// Fall back to deprecated field for backward compatibility
|
||||
return node.OtelTraceSQLEnabled
|
||||
}
|
||||
|
||||
@ -33,6 +33,7 @@ const (
|
||||
traceEventDbExecutionRows = "db.execution.rows"
|
||||
traceEventDbExecutionTxID = "db.execution.txid"
|
||||
traceEventDbExecutionType = "db.execution.type"
|
||||
traceEventDbExecutionSQL = "db.execution.sql"
|
||||
)
|
||||
|
||||
// addSqlToTracing adds sql information to tracer if it's enabled.
|
||||
@ -80,5 +81,11 @@ func (c *Core) traceSpanEnd(ctx context.Context, span trace.Span, sql *Sql) {
|
||||
}
|
||||
}
|
||||
events = append(events, attribute.String(traceEventDbExecutionType, string(sql.Type)))
|
||||
|
||||
// Add SQL statement to tracing if enabled
|
||||
if c.db.GetConfig().IsOtelTraceSQLEnabled() {
|
||||
events = append(events, attribute.String(traceEventDbExecutionSQL, sql.Format))
|
||||
}
|
||||
|
||||
span.AddEvent(traceEventDbExecution, trace.WithAttributes(events...))
|
||||
}
|
||||
|
||||
90
database/gdb/gdb_z_unit_feature_otel_tracing_test.go
Normal file
90
database/gdb/gdb_z_unit_feature_otel_tracing_test.go
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/internal/otel"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
func Test_OTEL_SQLTracing_Default(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
config := gdb.ConfigNode{
|
||||
Type: "sqlite",
|
||||
Name: ":memory:",
|
||||
}
|
||||
|
||||
// By default, SQL tracing should be disabled
|
||||
t.Assert(config.IsOtelTraceSQLEnabled(), false)
|
||||
t.Assert(config.OtelTraceSQLEnabled, false)
|
||||
t.Assert(config.Otel.TraceSQLEnabled, false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OTEL_SQLTracing_Configuration(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
config := gdb.ConfigNode{
|
||||
Type: "sqlite",
|
||||
Name: ":memory:",
|
||||
OtelTraceSQLEnabled: true,
|
||||
}
|
||||
|
||||
// SQL tracing should be configurable using legacy field
|
||||
t.Assert(config.IsOtelTraceSQLEnabled(), true)
|
||||
t.Assert(config.OtelTraceSQLEnabled, true)
|
||||
t.Assert(config.Otel.TraceSQLEnabled, false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OTEL_SQLTracing_NewConfiguration(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
config := gdb.ConfigNode{
|
||||
Type: "sqlite",
|
||||
Name: ":memory:",
|
||||
Otel: otel.Config{
|
||||
TraceSQLEnabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
// SQL tracing should be configurable using new configuration
|
||||
t.Assert(config.IsOtelTraceSQLEnabled(), true)
|
||||
t.Assert(config.OtelTraceSQLEnabled, false)
|
||||
t.Assert(config.Otel.TraceSQLEnabled, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OTEL_SQLTracing_Enabled(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
config := gdb.ConfigNode{
|
||||
Type: "mysql",
|
||||
Name: "test_db",
|
||||
OtelTraceSQLEnabled: true,
|
||||
}
|
||||
|
||||
// Test that the configuration field can be set and retrieved using legacy field
|
||||
t.Assert(config.IsOtelTraceSQLEnabled(), true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OTEL_SQLTracing_BothFieldsEnabled(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
config := gdb.ConfigNode{
|
||||
Type: "mysql",
|
||||
Name: "test_db",
|
||||
OtelTraceSQLEnabled: false,
|
||||
Otel: otel.Config{
|
||||
TraceSQLEnabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
// New field should take precedence over legacy field
|
||||
t.Assert(config.IsOtelTraceSQLEnabled(), true)
|
||||
})
|
||||
}
|
||||
42
internal/otel/otel_config.go
Normal file
42
internal/otel/otel_config.go
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package otel provides OpenTelemetry configurations and utilities.
|
||||
package otel
|
||||
|
||||
// Config holds OpenTelemetry configuration options.
|
||||
type Config struct {
|
||||
// TraceSQLEnabled enables OpenTelemetry tracing for SQL operations.
|
||||
TraceSQLEnabled bool `json:"traceSQLEnabled"`
|
||||
// TraceRequestEnabled enables tracing of HTTP request parameters.
|
||||
TraceRequestEnabled bool `json:"traceRequestEnabled"`
|
||||
// TraceResponseEnabled enables tracing of HTTP response parameters.
|
||||
TraceResponseEnabled bool `json:"traceResponseEnabled"`
|
||||
}
|
||||
|
||||
// NewConfig creates and returns a new OTEL configuration with default settings.
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
TraceSQLEnabled: false,
|
||||
TraceRequestEnabled: false,
|
||||
TraceResponseEnabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
// IsTracingSQLEnabled returns whether SQL tracing is enabled.
|
||||
func (c *Config) IsTracingSQLEnabled() bool {
|
||||
return c.TraceSQLEnabled
|
||||
}
|
||||
|
||||
// IsTracingRequestEnabled returns whether HTTP request tracing is enabled.
|
||||
func (c *Config) IsTracingRequestEnabled() bool {
|
||||
return c.TraceRequestEnabled
|
||||
}
|
||||
|
||||
// IsTracingResponseEnabled returns whether HTTP response tracing is enabled.
|
||||
func (c *Config) IsTracingResponseEnabled() bool {
|
||||
return c.TraceResponseEnabled
|
||||
}
|
||||
@ -20,6 +20,7 @@ import (
|
||||
"github.com/gogf/gf/v2/internal/httputil"
|
||||
"github.com/gogf/gf/v2/net/gtrace"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
@ -28,9 +29,12 @@ const (
|
||||
tracingEventHttpRequest = "http.request"
|
||||
tracingEventHttpRequestHeaders = "http.request.headers"
|
||||
tracingEventHttpRequestBaggage = "http.request.baggage"
|
||||
tracingEventHttpRequestParams = "http.request.params"
|
||||
tracingEventHttpResponse = "http.response"
|
||||
tracingEventHttpResponseHeaders = "http.response.headers"
|
||||
tracingEventHttpResponseBody = "http.response.body"
|
||||
tracingEventHttpRequestUrl = "http.request.url"
|
||||
tracingEventHttpMethod = "http.method"
|
||||
tracingMiddlewareHandled gctx.StrKey = `MiddlewareServerTracingHandled`
|
||||
)
|
||||
|
||||
@ -75,11 +79,44 @@ func internalMiddlewareServerTracing(r *Request) {
|
||||
return
|
||||
}
|
||||
|
||||
span.AddEvent(tracingEventHttpRequest, trace.WithAttributes(
|
||||
// Basic trace attributes for all requests
|
||||
traceAttrs := []attribute.KeyValue{
|
||||
attribute.String(tracingEventHttpRequestUrl, r.URL.String()),
|
||||
attribute.String(tracingEventHttpMethod, r.Method),
|
||||
attribute.String(tracingEventHttpRequestHeaders, gconv.String(httputil.HeaderToMap(r.Header))),
|
||||
attribute.String(tracingEventHttpRequestBaggage, gtrace.GetBaggageMap(ctx).String()),
|
||||
))
|
||||
}
|
||||
|
||||
// Add request parameters if configured
|
||||
if r.Server != nil && r.Server.config.IsOtelTraceRequestEnabled() {
|
||||
// Get all request parameters (query + form + body)
|
||||
requestParams := make(map[string]any)
|
||||
|
||||
// Query parameters
|
||||
for k, v := range r.URL.Query() {
|
||||
requestParams[k] = v
|
||||
}
|
||||
|
||||
// Form parameters
|
||||
if r.ContentLength > 0 && gtrace.MaxContentLogSize() > 0 {
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
if gstr.Contains(contentType, "application/x-www-form-urlencoded") || gstr.Contains(contentType, "multipart/form-data") {
|
||||
// Use GetFormMap() instead of ParseForm() to get form data
|
||||
formData := r.GetFormMap()
|
||||
for k, v := range formData {
|
||||
requestParams[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(requestParams) > 0 {
|
||||
traceAttrs = append(traceAttrs,
|
||||
attribute.String(tracingEventHttpRequestParams, gconv.String(requestParams)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
span.AddEvent(tracingEventHttpRequest, trace.WithAttributes(traceAttrs...))
|
||||
|
||||
// Continue executing.
|
||||
r.Middleware.Next()
|
||||
@ -94,10 +131,27 @@ func internalMiddlewareServerTracing(r *Request) {
|
||||
span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err))
|
||||
}
|
||||
|
||||
span.AddEvent(tracingEventHttpResponse, trace.WithAttributes(
|
||||
// Response tracing attributes
|
||||
responseAttrs := []attribute.KeyValue{
|
||||
attribute.String(
|
||||
tracingEventHttpResponseHeaders,
|
||||
gconv.String(httputil.HeaderToMap(r.Response.Header())),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
// Add response body if configured
|
||||
if r.Server != nil && r.Server.config.IsOtelTraceResponseEnabled() {
|
||||
if r.Response.BufferLength() > 0 {
|
||||
responseBody := r.Response.BufferString()
|
||||
// Limit response body size for tracing to avoid memory issues
|
||||
if len(responseBody) > gtrace.MaxContentLogSize() {
|
||||
responseBody = responseBody[:gtrace.MaxContentLogSize()] + "...[truncated]"
|
||||
}
|
||||
responseAttrs = append(responseAttrs,
|
||||
attribute.String(tracingEventHttpResponseBody, responseBody),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
span.AddEvent(tracingEventHttpResponse, trace.WithAttributes(responseAttrs...))
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/internal/otel"
|
||||
"github.com/gogf/gf/v2/net/gsvc"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
@ -243,6 +244,21 @@ type ServerConfig struct {
|
||||
// GracefulShutdownTimeout set the maximum survival time (seconds) before stopping the server.
|
||||
GracefulShutdownTimeout int `json:"gracefulShutdownTimeout"`
|
||||
|
||||
// ======================================================================================================
|
||||
// OpenTelemetry Tracing.
|
||||
// ======================================================================================================
|
||||
|
||||
// Otel specifies the OpenTelemetry tracing configuration
|
||||
Otel otel.Config `json:"otel"`
|
||||
|
||||
// OtelTraceRequestEnabled enables tracing of HTTP request parameters.
|
||||
// Deprecated: Use Otel.TraceRequestEnabled instead. This field is kept for backward compatibility.
|
||||
OtelTraceRequestEnabled bool `json:"otelTraceRequestEnabled"`
|
||||
|
||||
// OtelTraceResponseEnabled enables tracing of HTTP response parameters.
|
||||
// Deprecated: Use Otel.TraceResponseEnabled instead. This field is kept for backward compatibility.
|
||||
OtelTraceResponseEnabled bool `json:"otelTraceResponseEnabled"`
|
||||
|
||||
// ======================================================================================================
|
||||
// Other.
|
||||
// ======================================================================================================
|
||||
@ -274,45 +290,48 @@ type ServerConfig struct {
|
||||
// some pointer attributes that may be shared in different servers.
|
||||
func NewConfig() ServerConfig {
|
||||
return ServerConfig{
|
||||
Name: DefaultServerName,
|
||||
Address: ":0",
|
||||
HTTPSAddr: "",
|
||||
Listeners: nil,
|
||||
Handler: nil,
|
||||
ReadTimeout: 60 * time.Second,
|
||||
WriteTimeout: 0, // No timeout.
|
||||
IdleTimeout: 60 * time.Second,
|
||||
MaxHeaderBytes: 10240, // 10KB
|
||||
KeepAlive: true,
|
||||
IndexFiles: []string{"index.html", "index.htm"},
|
||||
IndexFolder: false,
|
||||
ServerAgent: "GoFrame HTTP Server",
|
||||
ServerRoot: "",
|
||||
StaticPaths: make([]staticPathItem, 0),
|
||||
FileServerEnabled: false,
|
||||
CookieMaxAge: time.Hour * 24 * 365,
|
||||
CookiePath: "/",
|
||||
CookieDomain: "",
|
||||
SessionIdName: "gfsessionid",
|
||||
SessionPath: gsession.DefaultStorageFilePath,
|
||||
SessionMaxAge: time.Hour * 24,
|
||||
SessionCookieOutput: true,
|
||||
SessionCookieMaxAge: time.Hour * 24,
|
||||
Logger: glog.New(),
|
||||
LogLevel: "all",
|
||||
LogStdout: true,
|
||||
ErrorStack: true,
|
||||
ErrorLogEnabled: true,
|
||||
ErrorLogPattern: "error-{Ymd}.log",
|
||||
AccessLogEnabled: false,
|
||||
AccessLogPattern: "access-{Ymd}.log",
|
||||
DumpRouterMap: true,
|
||||
ClientMaxBodySize: 8 * 1024 * 1024, // 8MB
|
||||
FormParsingMemory: 1024 * 1024, // 1MB
|
||||
Rewrites: make(map[string]string),
|
||||
Graceful: false,
|
||||
GracefulTimeout: 2, // seconds
|
||||
GracefulShutdownTimeout: 5, // seconds
|
||||
Name: DefaultServerName,
|
||||
Address: ":0",
|
||||
HTTPSAddr: "",
|
||||
Listeners: nil,
|
||||
Handler: nil,
|
||||
ReadTimeout: 60 * time.Second,
|
||||
WriteTimeout: 0, // No timeout.
|
||||
IdleTimeout: 60 * time.Second,
|
||||
MaxHeaderBytes: 10240, // 10KB
|
||||
KeepAlive: true,
|
||||
IndexFiles: []string{"index.html", "index.htm"},
|
||||
IndexFolder: false,
|
||||
ServerAgent: "GoFrame HTTP Server",
|
||||
ServerRoot: "",
|
||||
StaticPaths: make([]staticPathItem, 0),
|
||||
FileServerEnabled: false,
|
||||
CookieMaxAge: time.Hour * 24 * 365,
|
||||
CookiePath: "/",
|
||||
CookieDomain: "",
|
||||
SessionIdName: "gfsessionid",
|
||||
SessionPath: gsession.DefaultStorageFilePath,
|
||||
SessionMaxAge: time.Hour * 24,
|
||||
SessionCookieOutput: true,
|
||||
SessionCookieMaxAge: time.Hour * 24,
|
||||
Logger: glog.New(),
|
||||
LogLevel: "all",
|
||||
LogStdout: true,
|
||||
ErrorStack: true,
|
||||
ErrorLogEnabled: true,
|
||||
ErrorLogPattern: "error-{Ymd}.log",
|
||||
AccessLogEnabled: false,
|
||||
AccessLogPattern: "access-{Ymd}.log",
|
||||
Otel: *otel.NewConfig(),
|
||||
OtelTraceRequestEnabled: false,
|
||||
OtelTraceResponseEnabled: false,
|
||||
DumpRouterMap: true,
|
||||
ClientMaxBodySize: 8 * 1024 * 1024, // 8MB
|
||||
FormParsingMemory: 1024 * 1024, // 1MB
|
||||
Rewrites: make(map[string]string),
|
||||
Graceful: false,
|
||||
GracefulTimeout: 2, // seconds
|
||||
GracefulShutdownTimeout: 5, // seconds
|
||||
}
|
||||
}
|
||||
|
||||
@ -573,3 +592,32 @@ func (s *Server) SetRegistrar(registrar gsvc.Registrar) {
|
||||
func (s *Server) GetRegistrar() gsvc.Registrar {
|
||||
return s.registrar
|
||||
}
|
||||
|
||||
// GetConfig returns the configuration of current server.
|
||||
func (s *Server) GetConfig() ServerConfig {
|
||||
return s.config
|
||||
}
|
||||
|
||||
// IsOtelTraceRequestEnabled returns whether HTTP request tracing is enabled.
|
||||
// It checks both the new Otel.TraceRequestEnabled field and the deprecated OtelTraceRequestEnabled field
|
||||
// for backward compatibility.
|
||||
func (c ServerConfig) IsOtelTraceRequestEnabled() bool {
|
||||
// Check new configuration first
|
||||
if c.Otel.TraceRequestEnabled {
|
||||
return true
|
||||
}
|
||||
// Fall back to deprecated field for backward compatibility
|
||||
return c.OtelTraceRequestEnabled
|
||||
}
|
||||
|
||||
// IsOtelTraceResponseEnabled returns whether HTTP response tracing is enabled.
|
||||
// It checks both the new Otel.TraceResponseEnabled field and the deprecated OtelTraceResponseEnabled field
|
||||
// for backward compatibility.
|
||||
func (c ServerConfig) IsOtelTraceResponseEnabled() bool {
|
||||
// Check new configuration first
|
||||
if c.Otel.TraceResponseEnabled {
|
||||
return true
|
||||
}
|
||||
// Fall back to deprecated field for backward compatibility
|
||||
return c.OtelTraceResponseEnabled
|
||||
}
|
||||
|
||||
254
net/ghttp/ghttp_z_unit_feature_otel_tracing_test.go
Normal file
254
net/ghttp/ghttp_z_unit_feature_otel_tracing_test.go
Normal file
@ -0,0 +1,254 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/internal/otel"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/guid"
|
||||
)
|
||||
|
||||
var testCtx = context.Background()
|
||||
|
||||
func Test_OTEL_RequestTracing_Disabled(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
s.BindHandler("/test", func(r *ghttp.Request) {
|
||||
r.Response.WriteJson(g.Map{"result": "ok"})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
|
||||
// By default, request tracing should be disabled
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
res, err := client.Post(ctx, "/test", g.Map{"param1": "value1"})
|
||||
t.AssertNil(err)
|
||||
defer res.Close()
|
||||
|
||||
t.Assert(res.StatusCode, 200)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OTEL_RequestTracing_Enabled(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
|
||||
// Enable request tracing using SetConfigWithMap
|
||||
err := s.SetConfigWithMap(g.Map{
|
||||
"OtelTraceRequestEnabled": true,
|
||||
})
|
||||
gtest.AssertNil(err)
|
||||
|
||||
s.BindHandler("/test", func(r *ghttp.Request) {
|
||||
r.Response.WriteJson(g.Map{"result": "ok"})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
res, err := client.Post(ctx, "/test?query1=qvalue1", g.Map{"param1": "value1"})
|
||||
t.AssertNil(err)
|
||||
defer res.Close()
|
||||
|
||||
t.Assert(res.StatusCode, 200)
|
||||
// Test passes if no errors occurred during tracing
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OTEL_ResponseTracing_Enabled(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
|
||||
// Enable response tracing using SetConfigWithMap
|
||||
err := s.SetConfigWithMap(g.Map{
|
||||
"OtelTraceResponseEnabled": true,
|
||||
})
|
||||
gtest.AssertNil(err)
|
||||
|
||||
s.BindHandler("/test", func(r *ghttp.Request) {
|
||||
r.Response.WriteJson(g.Map{"result": "success", "data": "test data"})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
res, err := client.Get(ctx, "/test")
|
||||
t.AssertNil(err)
|
||||
defer res.Close()
|
||||
|
||||
t.Assert(res.StatusCode, 200)
|
||||
// Test passes if no errors occurred during response tracing
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OTEL_BothTracingEnabled(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
|
||||
// Enable both request and response tracing using SetConfigWithMap
|
||||
err := s.SetConfigWithMap(g.Map{
|
||||
"OtelTraceRequestEnabled": true,
|
||||
"OtelTraceResponseEnabled": true,
|
||||
})
|
||||
gtest.AssertNil(err)
|
||||
|
||||
s.BindHandler("/test", func(r *ghttp.Request) {
|
||||
r.Response.WriteJson(g.Map{
|
||||
"received_param": r.Get("param1"),
|
||||
"received_query": r.Get("query1"),
|
||||
"result": "success",
|
||||
})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
res, err := client.Post(ctx, "/test?query1=testquery", g.Map{"param1": "testparam"})
|
||||
t.AssertNil(err)
|
||||
defer res.Close()
|
||||
|
||||
t.Assert(res.StatusCode, 200)
|
||||
// Test passes if no errors occurred during both request and response tracing
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OTEL_NewConfiguration_RequestTracing(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
|
||||
// Enable request tracing using new independent OTEL configuration
|
||||
config := ghttp.NewConfig()
|
||||
config.Otel.TraceRequestEnabled = true
|
||||
err := s.SetConfig(config)
|
||||
gtest.AssertNil(err)
|
||||
|
||||
s.BindHandler("/test", func(r *ghttp.Request) {
|
||||
r.Response.WriteJson(g.Map{"result": "ok"})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
res, err := client.Post(ctx, "/test?query1=qvalue1", g.Map{"param1": "value1"})
|
||||
t.AssertNil(err)
|
||||
defer res.Close()
|
||||
|
||||
t.Assert(res.StatusCode, 200)
|
||||
// Test configuration helper methods
|
||||
t.Assert(s.GetConfig().IsOtelTraceRequestEnabled(), true)
|
||||
t.Assert(s.GetConfig().IsOtelTraceResponseEnabled(), false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OTEL_NewConfiguration_ResponseTracing(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
|
||||
// Enable response tracing using new independent OTEL configuration
|
||||
config := ghttp.NewConfig()
|
||||
config.Otel.TraceResponseEnabled = true
|
||||
err := s.SetConfig(config)
|
||||
gtest.AssertNil(err)
|
||||
|
||||
s.BindHandler("/test", func(r *ghttp.Request) {
|
||||
r.Response.WriteJson(g.Map{"result": "success", "data": "test data"})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
res, err := client.Get(ctx, "/test")
|
||||
t.AssertNil(err)
|
||||
defer res.Close()
|
||||
|
||||
t.Assert(res.StatusCode, 200)
|
||||
// Test configuration helper methods
|
||||
t.Assert(s.GetConfig().IsOtelTraceRequestEnabled(), false)
|
||||
t.Assert(s.GetConfig().IsOtelTraceResponseEnabled(), true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OTEL_BackwardCompatibility(t *testing.T) {
|
||||
s := g.Server(guid.S())
|
||||
|
||||
// Test that legacy configuration still works alongside new configuration
|
||||
config := ghttp.NewConfig()
|
||||
config.OtelTraceRequestEnabled = true // Legacy field
|
||||
config.Otel.TraceResponseEnabled = true // New field
|
||||
err := s.SetConfig(config)
|
||||
gtest.AssertNil(err)
|
||||
|
||||
s.BindHandler("/test", func(r *ghttp.Request) {
|
||||
r.Response.WriteJson(g.Map{"result": "backward_compatible"})
|
||||
})
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
res, err := client.Post(ctx, "/test?query=test", g.Map{"param": "test"})
|
||||
t.AssertNil(err)
|
||||
defer res.Close()
|
||||
|
||||
t.Assert(res.StatusCode, 200)
|
||||
// Test that both legacy and new configuration work together
|
||||
t.Assert(s.GetConfig().IsOtelTraceRequestEnabled(), true)
|
||||
t.Assert(s.GetConfig().IsOtelTraceResponseEnabled(), true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OTEL_Configuration_Helpers(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Test new OTEL config helpers
|
||||
otelConfig := otel.NewConfig()
|
||||
t.Assert(otelConfig.IsTracingSQLEnabled(), false)
|
||||
t.Assert(otelConfig.IsTracingRequestEnabled(), false)
|
||||
t.Assert(otelConfig.IsTracingResponseEnabled(), false)
|
||||
|
||||
otelConfig.TraceSQLEnabled = true
|
||||
otelConfig.TraceRequestEnabled = true
|
||||
t.Assert(otelConfig.IsTracingSQLEnabled(), true)
|
||||
t.Assert(otelConfig.IsTracingRequestEnabled(), true)
|
||||
t.Assert(otelConfig.IsTracingResponseEnabled(), false)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user