Documentation
¶
Overview ¶
Package events captures the server-emitted analytics events of milestone 07. Handlers build events with the typed constructors and hand them to a Writer, whose Emit never blocks the calling RPC: events are buffered in a bounded channel and batch-inserted by a background goroutine, with drop-on-overflow rather than back-pressure.
The package is self-contained: it defines its own narrow store interface (BatchInserter) and does not import the rest of the server.
Index ¶
- Constants
- func ParsePlatform(raw string) string
- func ParseSDKVersion(raw string) string
- func WithClientInfo(ctx context.Context, info ClientInfo) context.Context
- type BatchInserter
- type ClientInfo
- type Config
- type Event
- func EmailVerified(ctx context.Context, projectID, userID string) Event
- func IdentityLinked(ctx context.Context, projectID, userID, provider string) Event
- func Login(ctx context.Context, projectID, userID, provider string) Event
- func LoginFailed(ctx context.Context, projectID, provider string, reason FailureReason) Event
- func PasswordResetCompleted(ctx context.Context, projectID, userID string) Event
- func Signup(ctx context.Context, projectID, userID, provider string) Event
- func TokenRefresh(ctx context.Context, projectID, userID string) Event
- func UserDeleted(ctx context.Context, projectID, userID string) Event
- type FailureReason
- type Stats
- type Writer
Constants ¶
const ( TypeUserSignup = "user.signup" TypeUserLogin = "user.login" TypeTokenRefresh = "token.refresh" TypeUserLoginFailed = "user.login_failed" TypePasswordResetCompleted = "password.reset_completed" TypeEmailVerified = "email.verified" TypeUserDeleted = "user.deleted" TypeIdentityLinked = "identity.linked" )
Event types emitted by the auth handlers. Authoritative — clients never send events, only ambient context in request metadata.
const ( PlatformHeader = "x-moth-platform" SDKVersionHeader = "x-moth-sdk-version" )
Request-metadata headers attached by the SDK client interceptor (milestone 05) on every call.
const ( PlatformIOS = "ios" PlatformAndroid = "android" PlatformWeb = "web" PlatformMacOS = "macos" PlatformWindows = "windows" PlatformLinux = "linux" PlatformOther = "other" )
Platforms recognized in x-moth-platform. Anything else non-empty (a future OS, garbage) is bucketed as "other"; a missing header stays "".
const DefaultTokenRefreshRate = 0.1
DefaultTokenRefreshRate is the default sampling rate for token.refresh events: 1 in 10.
Variables ¶
This section is empty.
Functions ¶
func ParsePlatform ¶
ParsePlatform validates a raw x-moth-platform value against the known enum (case-insensitively). Unknown non-empty values become "other" so a hostile client cannot inject arbitrary strings into analytics; an absent header stays "".
func ParseSDKVersion ¶
ParseSDKVersion sanitizes a raw x-moth-sdk-version value: trimmed, at most maxSDKVersionLen bytes, and restricted to the characters found in semver-ish strings ([0-9A-Za-z .+_-]). Anything else returns "" — a version header is best-effort context, never worth storing garbage.
func WithClientInfo ¶
func WithClientInfo(ctx context.Context, info ClientInfo) context.Context
WithClientInfo stashes info in ctx for the event constructors to pick up. The auth interceptor calls this once per request.
Types ¶
type BatchInserter ¶
BatchInserter is the narrow store surface the writer needs. The store package implements it in milestone 07; tests use fakes. The events slice is owned by the callee — the writer never reuses or mutates it after the call.
type ClientInfo ¶
type ClientInfo struct {
Platform string // one of the Platform* constants, or ""
SDKVersion string // sanitized version string, or ""
}
ClientInfo is the ambient client context extracted from request headers.
func ClientInfoFromContext ¶
func ClientInfoFromContext(ctx context.Context) ClientInfo
ClientInfoFromContext returns the info stored by WithClientInfo, or the zero value when none was stored.
func ClientInfoFromHeader ¶
func ClientInfoFromHeader(h http.Header) ClientInfo
ClientInfoFromHeader parses and validates the SDK metadata headers. connect exposes request metadata as http.Header on both gRPC and gRPC-web transports.
type Config ¶
type Config struct {
// BufferSize bounds the in-flight event channel. When it is full,
// Emit drops the event and increments Stats.Dropped — auth latency
// is never traded for analytics completeness. Default 4096.
BufferSize int
// BatchSize triggers a flush once this many events are buffered.
// Default 128.
BatchSize int
// FlushInterval flushes partial batches so a quiet server still
// lands events promptly. Default 2s.
FlushInterval time.Duration
// SampleRates maps an event type to the fraction of its events kept
// at Emit time, e.g. {TypeTokenRefresh: 0.1}. Types absent from the
// map are always kept. A user's first sampled-type event of the UTC
// day is always kept regardless of the rate: the DAU aggregation
// counts distinct users, so purely random sampling would hide a
// once-a-day refresher from DAU ~90% of their active days — keeping
// the daily first preserves per-user presence while the rate still
// thins the volume. Default samples token.refresh at
// DefaultTokenRefreshRate.
SampleRates map[string]float64
// Logger receives insert-failure warnings and the rate-limited
// lost-events warning. Default slog.Default().
Logger *slog.Logger
// LostLogInterval rate-limits the warning emitted when events were
// dropped (full buffer) or failed to insert since the last report, so
// silent analytics loss is visible in production logs. Default 1m.
LostLogInterval time.Duration
}
Config tunes a Writer. The zero value gets sensible defaults from NewWriter.
type Event ¶
type Event struct {
Type string
ProjectID string
UserID string
Provider string
Platform string
SDKVersion string
// Metadata is a small bag of extra fields (e.g. the login-failure
// reason). It is JSON-encoded at write time via MetadataJSON.
Metadata map[string]string
CreatedAt time.Time
}
Event is one analytics event, mirroring the events table: who did what, on which platform, when. UserID, Provider, Platform, SDKVersion and Metadata are all optional.
func EmailVerified ¶
EmailVerified records a confirmed email address.
func IdentityLinked ¶
IdentityLinked records a social identity being attached to an existing account.
func LoginFailed ¶
func LoginFailed(ctx context.Context, projectID, provider string, reason FailureReason) Event
LoginFailed records a failed sign-in. By construction it carries no user ID — only the coarse reason bucket in metadata — so failure analytics can never identify an account.
func PasswordResetCompleted ¶
PasswordResetCompleted records a finished password reset.
func TokenRefresh ¶
TokenRefresh records a refresh-token exchange. Writers keep each user's first refresh of the day and sample the rest (see Config.SampleRates): the type exists to approximate DAU — which needs every active user, not every refresh.
func UserDeleted ¶
UserDeleted records an account deletion.
func (Event) MetadataJSON ¶
MetadataJSON returns the metadata encoded as a JSON object, or "" when there is none. Encoding failures cannot happen for map[string]string.
type FailureReason ¶
type FailureReason string
FailureReason buckets why a login failed. Only these coarse buckets are stored — never the raw error, never an identifier of the account.
const ( ReasonInvalidCredentials FailureReason = "invalid_credentials" ReasonDisabled FailureReason = "disabled" ReasonProviderDisabled FailureReason = "provider_disabled" ReasonOther FailureReason = "other" )
type Stats ¶
type Stats struct {
// Enqueued counts events accepted into the buffer.
Enqueued uint64
// Written counts events successfully inserted.
Written uint64
// Dropped counts events lost to a full buffer (or emitted after
// Close).
Dropped uint64
// SampledOut counts events discarded by the sampler.
SampledOut uint64
// Failed counts events lost because a batch insert errored.
Failed uint64
}
Stats is a snapshot of the writer's atomic counters.
type Writer ¶
type Writer struct {
// contains filtered or unexported fields
}
Writer batches analytics events into a BatchInserter from a background goroutine. Emit never blocks. Create with NewWriter, stop with Close.
func NewWriter ¶
func NewWriter(dst BatchInserter, cfg Config) *Writer
NewWriter starts the background flusher and returns the writer.
func (*Writer) Close ¶
Close stops the writer, draining buffered events until ctx expires. After the deadline any in-flight insert is cancelled and remaining events are abandoned; ctx.Err() is returned. Emit after Close drops. Close is idempotent and safe to call concurrently.