Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
151
backend/internal/logging/cpa_trace.go
Normal file
151
backend/internal/logging/cpa_trace.go
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// CPATraceIDHeader is the downstream response header used to correlate requests with selected credentials.
|
||||
const CPATraceIDHeader = "X-CPA-TRACE-ID"
|
||||
|
||||
const ginCPATraceStateKey = "__cpa_trace_state__"
|
||||
|
||||
// FormatCPATraceID builds a CPA trace ID from the selection time, auth index, and request ID.
|
||||
func FormatCPATraceID(selectedAt time.Time, authIndex, requestID string) string {
|
||||
authIndex = strings.TrimSpace(authIndex)
|
||||
requestID = strings.TrimSpace(requestID)
|
||||
if selectedAt.IsZero() || authIndex == "" || requestID == "" {
|
||||
return ""
|
||||
}
|
||||
return selectedAt.Format("20060102150405") + "-" + authIndex + "-" + requestID
|
||||
}
|
||||
|
||||
type cpaTraceState struct {
|
||||
mu sync.RWMutex
|
||||
traceID string
|
||||
}
|
||||
|
||||
func (s *cpaTraceState) set(traceID string) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.traceID = strings.TrimSpace(traceID)
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *cpaTraceState) get() string {
|
||||
if s == nil {
|
||||
return ""
|
||||
}
|
||||
s.mu.RLock()
|
||||
traceID := s.traceID
|
||||
s.mu.RUnlock()
|
||||
return traceID
|
||||
}
|
||||
|
||||
func ginCPATraceState(c *gin.Context) *cpaTraceState {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
if value, exists := c.Get(ginCPATraceStateKey); exists {
|
||||
if state, ok := value.(*cpaTraceState); ok && state != nil {
|
||||
return state
|
||||
}
|
||||
}
|
||||
state := &cpaTraceState{}
|
||||
c.Set(ginCPATraceStateKey, state)
|
||||
return state
|
||||
}
|
||||
|
||||
// GinCPATraceIDCallback returns a callback that is safe to invoke after the Gin context is released.
|
||||
func GinCPATraceIDCallback(c *gin.Context) func(string) {
|
||||
state := ginCPATraceState(c)
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
requestID := GetGinRequestID(c)
|
||||
if requestID == "" && c.Request != nil {
|
||||
requestID = GetRequestID(c.Request.Context())
|
||||
}
|
||||
requestID = strings.TrimSpace(requestID)
|
||||
if requestID == "" {
|
||||
return nil
|
||||
}
|
||||
return func(authIndex string) {
|
||||
if traceID := FormatCPATraceID(time.Now(), authIndex, requestID); traceID != "" {
|
||||
state.set(traceID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetGinCPATraceID stores the trace ID until the downstream response headers are committed.
|
||||
func SetGinCPATraceID(c *gin.Context, authIndex string) {
|
||||
if callback := GinCPATraceIDCallback(c); callback != nil {
|
||||
callback(authIndex)
|
||||
}
|
||||
}
|
||||
|
||||
// GetGinCPATraceID returns the trace ID stored for the current request.
|
||||
func GetGinCPATraceID(c *gin.Context) string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
value, exists := c.Get(ginCPATraceStateKey)
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
state, _ := value.(*cpaTraceState)
|
||||
return state.get()
|
||||
}
|
||||
|
||||
// CPATraceIDMiddleware injects a stored trace ID immediately before response headers are committed.
|
||||
func CPATraceIDMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
state := ginCPATraceState(c)
|
||||
c.Writer = &cpaTraceResponseWriter{ResponseWriter: c.Writer, state: state}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
type cpaTraceResponseWriter struct {
|
||||
gin.ResponseWriter
|
||||
state *cpaTraceState
|
||||
}
|
||||
|
||||
func (w *cpaTraceResponseWriter) WriteHeader(statusCode int) {
|
||||
w.applyTraceHeader()
|
||||
w.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func (w *cpaTraceResponseWriter) WriteHeaderNow() {
|
||||
w.applyTraceHeader()
|
||||
w.ResponseWriter.WriteHeaderNow()
|
||||
}
|
||||
|
||||
func (w *cpaTraceResponseWriter) Write(data []byte) (int, error) {
|
||||
w.applyTraceHeader()
|
||||
return w.ResponseWriter.Write(data)
|
||||
}
|
||||
|
||||
func (w *cpaTraceResponseWriter) WriteString(data string) (int, error) {
|
||||
w.applyTraceHeader()
|
||||
return w.ResponseWriter.WriteString(data)
|
||||
}
|
||||
|
||||
func (w *cpaTraceResponseWriter) Flush() {
|
||||
w.applyTraceHeader()
|
||||
w.ResponseWriter.Flush()
|
||||
}
|
||||
|
||||
func (w *cpaTraceResponseWriter) applyTraceHeader() {
|
||||
if w == nil || w.ResponseWriter == nil || w.ResponseWriter.Written() {
|
||||
return
|
||||
}
|
||||
if traceID := w.state.get(); traceID != "" {
|
||||
w.ResponseWriter.Header().Set(CPATraceIDHeader, traceID)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue