Stage 7 · Master
Phase 6 — Flat Service
Monitoring
Counting flats, not just requests
What Generic HTTP Metrics Miss
Organization Service's metrics lesson owns the private registry, route-template HTTP histogram, and /metrics wiring; User Service already re-applied that contract. Flat Service adds a domain signal for abnormal inventory creation, labeled by tenant_id because the operator needs to know which HOA is creating units unusually fast.
Business Counter for Inventory Creation
package observability
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var FlatsCreatedTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "flat_service_flats_created_total",
Help: "Total flats created, labeled by tenant.",
}, []string{"tenant_id"})tenant_id is intentionally present on this single counter: the time series count grows with tenants only for the inventory action an operator investigates.
func (h *FlatHandler) Create(c *gin.Context) {
tenantID := tenantctx.MustFromContext(c.Request.Context())
// ... binding and validation unchanged ...
flat, err := h.svc.Create(c.Request.Context(), domain.Flat{ /* ... */ })
if err != nil {
// ... error handling unchanged ...
return
}
observability.FlatsCreatedTotal.WithLabelValues(tenantID.String()).Inc()
c.JSON(http.StatusCreated, flat)
}Structured Logs Carry the Same Identifiers
case err != nil:
log.Error().
Str("tenant_id", tenantID.String()).
Str("building_id", req.BuildingID.String()).
Err(err).
Msg("could not create flat")
c.JSON(http.StatusInternalServerError, gin.H{"error": "could not create flat"})Every log line an operator reads after an incident should carry tenant_id — without it, 'flat-service errored for someone' is not actionable.
Inventory Counter Drill
This counter does not explain why inventory is being created. It gives the on-call engineer the tenant and rate needed to decide whether to inspect an import job, a retry loop, or a legitimate onboarding event.
Applied exercise
Alert on a tenant hammering the create endpoint
One tenant's integration accidentally retries every create request on any non-2xx response, including 409 Conflict, generating 40x the normal request volume for that tenant alone. Nothing today would page anyone about it.
- Write a PromQL expression that computes
rate(flat_service_flats_created_total[5m])per tenant_id. - Define an alerting rule that fires when one tenant's creation rate exceeds 10x the recent tenant median, not a fixed absolute threshold.
- Document, in one paragraph, why an absolute per-tenant threshold is the wrong choice for a multi-tenant platform with HOAs of very different sizes.
Deliverable
A PromQL alerting rule (as YAML) plus the explanatory paragraph.
Completion checks
- The rule is relative (ratio to median or similar), not a fixed absolute number.
- The rule uses flat_service_flats_created_total rather than the generic HTTP histogram.
- The explanation names the specific failure mode an absolute threshold would cause: false alerts for large tenants, or missed alerts for small ones.
Why is tenant_id a label on FlatsCreatedTotal but not on RequestDuration?
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.