http.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. package servertiming
  2. import (
  3. "context"
  4. "net/http"
  5. "strings"
  6. "time"
  7. )
  8. type dependencyModuleKey struct{}
  9. type timingRoundTripper struct {
  10. base http.RoundTripper
  11. }
  12. // WithDependencyModule overrides the safe module name used for an outbound call.
  13. func WithDependencyModule(ctx context.Context, module string) context.Context {
  14. if ctx == nil {
  15. ctx = context.Background()
  16. }
  17. module = strings.TrimPrefix(normalizeMetricName(module), dependencyPrefix)
  18. if module == "" {
  19. return ctx
  20. }
  21. return context.WithValue(ctx, dependencyModuleKey{}, module)
  22. }
  23. // WrapRoundTripper records outbound response-header latency for active requests.
  24. func WrapRoundTripper(base http.RoundTripper) http.RoundTripper {
  25. if base == nil {
  26. base = http.DefaultTransport
  27. }
  28. if _, ok := base.(*timingRoundTripper); ok {
  29. return base
  30. }
  31. return &timingRoundTripper{base: base}
  32. }
  33. // InstrumentClient returns a shallow client copy with an instrumented transport.
  34. func InstrumentClient(client *http.Client) *http.Client {
  35. if client == nil {
  36. client = &http.Client{}
  37. }
  38. copyClient := *client
  39. copyClient.Transport = WrapRoundTripper(copyClient.Transport)
  40. return &copyClient
  41. }
  42. // Do records response-header latency without changing the client's transport
  43. // type. Use it for clients whose callers inspect or configure *http.Transport.
  44. func Do(client *http.Client, req *http.Request) (*http.Response, error) {
  45. if client == nil {
  46. client = http.DefaultClient
  47. }
  48. if req == nil || !Active(req.Context()) {
  49. return client.Do(req) //nolint:gosec // G704: 通用埋点包装器,不构造 URL;请求由调用方构造,SSRF 信任边界在调用方
  50. }
  51. startedAt := time.Now()
  52. response, err := client.Do(req) //nolint:gosec // G704: 同上
  53. RecordDependency(req.Context(), dependencyModule(req), startedAt, time.Now())
  54. return response, err
  55. }
  56. func (t *timingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
  57. if req == nil || !Active(req.Context()) {
  58. return t.base.RoundTrip(req)
  59. }
  60. startedAt := time.Now()
  61. response, err := t.base.RoundTrip(req)
  62. RecordDependency(req.Context(), dependencyModule(req), startedAt, time.Now())
  63. return response, err
  64. }
  65. func dependencyModule(req *http.Request) string {
  66. if req != nil {
  67. if module, ok := req.Context().Value(dependencyModuleKey{}).(string); ok && module != "" {
  68. return module
  69. }
  70. }
  71. if req == nil || req.URL == nil {
  72. return "http"
  73. }
  74. host := strings.ToLower(req.URL.Hostname())
  75. switch {
  76. case strings.Contains(host, "github"):
  77. return "github"
  78. case strings.Contains(host, "openai"):
  79. return "openai"
  80. case strings.Contains(host, "anthropic"):
  81. return "anthropic"
  82. case strings.Contains(host, "generativelanguage") || strings.Contains(host, "gemini"):
  83. return "gemini"
  84. case strings.Contains(host, "cloudcode") || strings.Contains(host, "antigravity"):
  85. return "antigravity"
  86. case strings.Contains(host, "googleapis") || strings.Contains(host, "google"):
  87. return "google"
  88. case strings.Contains(host, "amazonaws") || strings.Contains(host, "cloudflarestorage") || strings.Contains(host, "s3"):
  89. return "s3"
  90. case strings.Contains(host, "stripe") || strings.Contains(host, "airwallex") || strings.Contains(host, "alipay") || strings.Contains(host, "wechatpay") || strings.Contains(host, "paypal"):
  91. return "payment"
  92. default:
  93. return "http"
  94. }
  95. }