feat(runtime): track upstream response headers in logging and usage reporting
- Added APIs to store, retrieve, and clone upstream response headers in context for detailed logging. - Updated `RecordAPIResponseMetadata`, `RecordAPIWebsocketHandshake`, and related methods to capture response headers. - Extended `UsageReporter` to include response headers in published usage records. - Enhanced payload tests to validate response headers' integrity and persistence. - Refactored `usage.Record` to support optional `ResponseHeaders` field.
This commit is contained in:
@@ -2,16 +2,24 @@ package logging
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type endpointKey struct{}
|
||||
type responseStatusKey struct{}
|
||||
type responseHeadersKey struct{}
|
||||
|
||||
type responseStatusHolder struct {
|
||||
status atomic.Int32
|
||||
}
|
||||
|
||||
type responseHeadersHolder struct {
|
||||
mu sync.RWMutex
|
||||
headers http.Header
|
||||
}
|
||||
|
||||
func WithEndpoint(ctx context.Context, endpoint string) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
@@ -39,6 +47,16 @@ func WithResponseStatusHolder(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, responseStatusKey{}, &responseStatusHolder{})
|
||||
}
|
||||
|
||||
func WithResponseHeadersHolder(ctx context.Context) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
if holder, ok := ctx.Value(responseHeadersKey{}).(*responseHeadersHolder); ok && holder != nil {
|
||||
return ctx
|
||||
}
|
||||
return context.WithValue(ctx, responseHeadersKey{}, &responseHeadersHolder{})
|
||||
}
|
||||
|
||||
func SetResponseStatus(ctx context.Context, status int) {
|
||||
if ctx == nil || status <= 0 {
|
||||
return
|
||||
@@ -50,6 +68,19 @@ func SetResponseStatus(ctx context.Context, status int) {
|
||||
holder.status.Store(int32(status))
|
||||
}
|
||||
|
||||
func SetResponseHeaders(ctx context.Context, headers http.Header) {
|
||||
if ctx == nil {
|
||||
return
|
||||
}
|
||||
holder, ok := ctx.Value(responseHeadersKey{}).(*responseHeadersHolder)
|
||||
if !ok || holder == nil {
|
||||
return
|
||||
}
|
||||
holder.mu.Lock()
|
||||
defer holder.mu.Unlock()
|
||||
holder.headers = cloneHTTPHeader(headers)
|
||||
}
|
||||
|
||||
func GetResponseStatus(ctx context.Context) int {
|
||||
if ctx == nil {
|
||||
return 0
|
||||
@@ -60,3 +91,27 @@ func GetResponseStatus(ctx context.Context) int {
|
||||
}
|
||||
return int(holder.status.Load())
|
||||
}
|
||||
|
||||
func GetResponseHeaders(ctx context.Context) http.Header {
|
||||
if ctx == nil {
|
||||
return nil
|
||||
}
|
||||
holder, ok := ctx.Value(responseHeadersKey{}).(*responseHeadersHolder)
|
||||
if !ok || holder == nil {
|
||||
return nil
|
||||
}
|
||||
holder.mu.RLock()
|
||||
defer holder.mu.RUnlock()
|
||||
return cloneHTTPHeader(holder.headers)
|
||||
}
|
||||
|
||||
func cloneHTTPHeader(src http.Header) http.Header {
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
}
|
||||
dst := make(http.Header, len(src))
|
||||
for key, values := range src {
|
||||
dst[key] = append([]string(nil), values...)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user