carlos / movement Public

Clone
git clone https://amadan.net/carlos/movement

Plain git — no account needed to clone.

Download

Download this file

1package movement
2
3import "log/slog"
4
5// This file is the only place in internal/movement that imports log/slog,
6// and a guard test in internal/movementtest enforces that.
7//
8// The reason is narrow and specific. An email address on its own is
9// ordinary and a stance on its own is anonymous, but the two together are a
10// political position attached to a named person — and `carlos logs` is
11// platform-held storage that cannot be grepped clean afterwards. The pair is
12// easy to assemble by accident: rastrillo/mail formats its own failures as
13// "send to <address>", so logging a send error verbatim next to a request
14// path carrying the stance would do it in one line.
15//
16// So the log surface takes a fixed vocabulary and nothing else. No error
17// values, no strings drawn from a request. Anything a caller might want to
18// say about a submission has to already be one of the Reason constants
19// below, which means adding a new one is a deliberate act rather than a
20// convenient interpolation.
21
22// Reason is why a submission was refused. The set is closed on purpose.
23type Reason string
24
25const (
26 ReasonSealInvalid Reason = "seal_invalid"
27 ReasonNonceSpent Reason = "nonce_spent"
28 ReasonTooFast Reason = "too_fast"
29 ReasonTooOld Reason = "too_old"
30 ReasonPowShort Reason = "pow_short"
31 ReasonHoneypot Reason = "honeypot"
32 ReasonBounds Reason = "bounds"
33 ReasonMailFailed Reason = "mail_failed"
34 ReasonMailBudget Reason = "mail_budget"
35)
36
37var logger = slog.Default()
38
39// SetLogger installs the process logger. Called once, from App.
40func SetLogger(l *slog.Logger) {
41 if l != nil {
42 logger = l
43 }
44}
45
46// Rejected records why a submission was refused. It deliberately takes no
47// address, no stance and no error — only the code.
48func Rejected(r Reason) {
49 logger.Info("signup rejected", "reason", string(r))
50}
51
52// Migrated is the one boot line about schema, and it exists because a
53// fleet-wide adoption — every database stamped, no DDL run, exactly once —
54// otherwise produces no output at all, which is the last event you want to
55// be silent.
56//
57// Typed rather than variadic so this file's rule needs no exception: there
58// is no way to hand it a string that came from a request. Migration names
59// are compile-time constants, so they are safe.
60func Migrated(applied []string, skipped int, adopted bool) {
61 logger.Info("migrate", "applied", applied, "skipped", skipped, "adopted", adopted)
62}
63
64// slogFor hands the raw logger to framework packages that require one —
65// rastrillo/mail and rastrillo/auth both take an *slog.Logger and there is no
66// way to give them anything else.
67//
68// This is the one hole in the rule above, and it is a known, bounded one: the
69// addresses those packages log are admins signing in, which are never paired
70// with a stance. Nothing in this app may call it to log its own lines.
71func slogFor() *slog.Logger { return logger }
72
73// Warn carries a fixed message. Callers pass literals, never request data.
74func Warn(msg string) {
75 logger.Warn(msg)
76}
77
78// Info carries a fixed message, same rule as Warn.
79func Info(msg string) {
80 logger.Info(msg)
81}
82