Files

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])
}