78 lines
3.0 KiB
Go
78 lines
3.0 KiB
Go
// Package telemetry contains operational projections that do not own domain
|
|
// state. Compatibility events are intentionally redacted and best-effort.
|
|
package telemetry
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"log"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// CompatibilityEvent is one bounded invocation observation. Aggregators can
|
|
// derive counts and unique request/session counts from the redacted keys.
|
|
// It deliberately contains no content, secret, or user identifier.
|
|
type CompatibilityEvent struct {
|
|
Event string `json:"event"`
|
|
Path string `json:"path"`
|
|
Component string `json:"component"`
|
|
Access string `json:"access"`
|
|
Reason string `json:"reason"`
|
|
Gen2PathAvailable bool `json:"gen2_path_available"`
|
|
Fallback bool `json:"fallback"`
|
|
Primary bool `json:"primary"`
|
|
Outcome string `json:"outcome"`
|
|
InvocationCount int `json:"invocation_count"`
|
|
UniqueRequestKey string `json:"unique_request_key"`
|
|
UniqueSessionKey string `json:"unique_session_key"`
|
|
SafeCorrelationID string `json:"correlation_id"`
|
|
LastUsedAt string `json:"last_used_at"`
|
|
ObservedAtUnixMillis int64 `json:"observed_at_unix_millis"`
|
|
}
|
|
|
|
// NewCompatibilityEvent creates a redacted event. requestRef/sessionRef are
|
|
// hashed immediately; callers must pass identifiers only, never content.
|
|
func NewCompatibilityEvent(path, component, access, reason string, gen2PathAvailable, fallback, primary bool, outcome, requestRef, sessionRef string, now time.Time) CompatibilityEvent {
|
|
if now.IsZero() {
|
|
now = time.Now().UTC()
|
|
}
|
|
requestKey := digest(requestRef)
|
|
sessionKey := digest(sessionRef)
|
|
correlationInput := requestRef + "\x00" + sessionRef + "\x00" + path + "\x00" + strconv.FormatInt(now.UnixMilli(), 10)
|
|
return CompatibilityEvent{
|
|
Event: "compatibility_observation",
|
|
Path: path,
|
|
Component: component,
|
|
Access: access,
|
|
Reason: reason,
|
|
Gen2PathAvailable: gen2PathAvailable,
|
|
Fallback: fallback,
|
|
Primary: primary,
|
|
Outcome: outcome,
|
|
InvocationCount: 1,
|
|
UniqueRequestKey: requestKey,
|
|
UniqueSessionKey: sessionKey,
|
|
SafeCorrelationID: digest(correlationInput),
|
|
LastUsedAt: now.UTC().Format(time.RFC3339Nano),
|
|
ObservedAtUnixMillis: now.UnixMilli(),
|
|
}
|
|
}
|
|
|
|
// RecordCompatibility emits a structured redacted event. Telemetry is
|
|
// best-effort and cannot change the compatibility path's result.
|
|
func RecordCompatibility(path, component, access, reason string, gen2PathAvailable, fallback, primary bool, outcome, requestRef, sessionRef string, now time.Time) {
|
|
event := NewCompatibilityEvent(path, component, access, reason, gen2PathAvailable, fallback, primary, outcome, requestRef, sessionRef, now)
|
|
payload, err := json.Marshal(event)
|
|
if err != nil {
|
|
return
|
|
}
|
|
log.Printf("%s", payload)
|
|
}
|
|
|
|
func digest(value string) string {
|
|
sum := sha256.Sum256([]byte(value))
|
|
return hex.EncodeToString(sum[:8])
|
|
}
|