8700fbfae1
- backend + gateway multi-stage distroless Dockerfiles; the gateway embeds and
serves the SPA at / and /telegram/ via go:embed (committed dist placeholder,
real build baked in by the image's node stage)
- deploy/docker-compose.yml: backend + gateway + Postgres + Telegram connector
(VPN sidecar) + OTel Collector + Prometheus (15d) + Tempo (72h) + Grafana,
fronted by a caddy owning a single /_gm Basic-Auth (admin console + Grafana
subpath); inter-service on a private network, only caddy on the edge network
- new metrics: backend accounts_created_total{kind} (robots excluded) and an
in-memory gateway active_users{window=24h,7d} gauge
- CI: single .gitea/workflows/ci.yaml (unit/integration/ui + a gated test-contour
deploy) on the new feature/* -> development -> master branch model; the old
go-unit/integration/ui-test workflows are folded in; the connector-scoped
compose is retired (superseded by deploy/)
- docs: ARCHITECTURE §11/§12/§13, root + gateway READMEs, CLAUDE.md branching,
PLAN.md (stage 16 done + refinements + Stage 17 forward-notes)
54 lines
1.8 KiB
Go
54 lines
1.8 KiB
Go
package account
|
|
|
|
import (
|
|
"context"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/metric"
|
|
"go.opentelemetry.io/otel/metric/noop"
|
|
)
|
|
|
|
// meterName scopes the account domain's OpenTelemetry instruments.
|
|
const meterName = "scrabble/backend/account"
|
|
|
|
// kindGuest labels guest accounts in accounts_created_total. Guests carry no
|
|
// identity, so they have no identity Kind; this is the metric label for them.
|
|
const kindGuest = "guest"
|
|
|
|
// accountMetrics holds the account domain's operational instruments. It defaults
|
|
// to no-ops (see defaultAccountMetrics); SetMetrics installs the real meter during
|
|
// startup wiring.
|
|
type accountMetrics struct {
|
|
created metric.Int64Counter
|
|
}
|
|
|
|
// defaultAccountMetrics returns instruments backed by a no-op meter.
|
|
func defaultAccountMetrics() *accountMetrics {
|
|
return newAccountMetrics(noop.NewMeterProvider().Meter(meterName))
|
|
}
|
|
|
|
// newAccountMetrics builds the instruments on meter, falling back to a no-op
|
|
// counter on the (rare) construction error.
|
|
func newAccountMetrics(meter metric.Meter) *accountMetrics {
|
|
c, err := meter.Int64Counter("accounts_created_total",
|
|
metric.WithDescription("New accounts created, labelled by kind (telegram/email/guest); robots are not counted."))
|
|
if err != nil {
|
|
c, _ = noop.NewMeterProvider().Meter(meterName).Int64Counter("accounts_created_total")
|
|
}
|
|
return &accountMetrics{created: c}
|
|
}
|
|
|
|
// SetMetrics installs the meter the account store records to. It must be called
|
|
// during startup wiring; the default is a no-op meter.
|
|
func (s *Store) SetMetrics(meter metric.Meter) {
|
|
if meter == nil {
|
|
return
|
|
}
|
|
s.metrics = newAccountMetrics(meter)
|
|
}
|
|
|
|
// recordCreated counts one newly created account of the given kind.
|
|
func (m *accountMetrics) recordCreated(ctx context.Context, kind string) {
|
|
m.created.Add(ctx, 1, metric.WithAttributes(attribute.String("kind", kind)))
|
|
}
|