feat(diary): component loggers through a redacting console and daily stack

- New diary module: For, Bootstrap, Setup, SetVerbosity, Reset, Close, ParseConfig
- Proxy component handler safe in package vars; styled console handler in bonfire's palette
- Daily writer: one file per day, 0640, lazy open, retention, disables itself on open failure
- Every channel leaf wrapped by sunscreen.Wrap; stack via slog.NewMultiHandler
- bonfire: ColorMode, Palette, DefaultPalette, DetectProfile (lipgloss v2, colorprofile)
- Generated main runs diary.Bootstrap first and diary.Setup after compass.Load
- Ignore /storage/ and /examples/hello/storage/
This commit is contained in:
Jakub Zych
2026-10-21 11:41:48 +02:00
parent e6039b445f
commit e903cc32be
18 changed files with 1676 additions and 23 deletions

View File

@@ -104,10 +104,10 @@ func generateMain(m Manifest) ([]byte, error) {
b.WriteString("\t\"git.golem15.com/golem15/summercms/modules/cabana\"\n")
b.WriteString("\t\"git.golem15.com/golem15/summercms/modules/compass\"\n")
b.WriteString("\t\"git.golem15.com/golem15/summercms/modules/conga\"\n")
b.WriteString("\t\"git.golem15.com/golem15/summercms/modules/diary\"\n")
b.WriteString("\t\"git.golem15.com/golem15/summercms/modules/lagoon\"\n")
b.WriteString("\t\"git.golem15.com/golem15/summercms/modules/pact\"\n")
b.WriteString("\t\"git.golem15.com/golem15/summercms/modules/party\"\n")
b.WriteString("\t\"git.golem15.com/golem15/summercms/modules/sunscreen\"\n")
b.WriteString("\t\"git.golem15.com/golem15/summercms/modules/surf\"\n")
b.WriteString(")\n\n")
b.WriteString("func main() {\n")
@@ -117,11 +117,14 @@ func generateMain(m Manifest) ([]byte, error) {
b.WriteString("\t}\n")
b.WriteString("}\n\n")
b.WriteString("func run(args []string, out io.Writer) error {\n")
b.WriteString("\tsunscreen.InstallDefault(os.Stderr)\n")
b.WriteString("\tdiary.Bootstrap(os.Stderr, diary.Options{})\n")
b.WriteString("\tcfg, err := compass.Load(\"config\")\n")
b.WriteString("\tif err != nil {\n")
b.WriteString("\t\treturn err\n")
b.WriteString("\t}\n")
b.WriteString("\tif err := diary.Setup(cfg, diary.Options{}); err != nil {\n")
b.WriteString("\t\treturn err\n")
b.WriteString("\t}\n")
b.WriteString("\tapp := backpack.New(cfg)\n")
b.WriteString("\tplugins, err := party.Activate(app, PluginIDs)\n")
b.WriteString("\tif err != nil {\n")

View File

@@ -142,29 +142,40 @@ func TestGenerateMainRegistersCongaRuntimeCommands(t *testing.T) {
}
}
// TestGenerateMainInstallsRedactingLogger: the first statement of run makes
// the credential-redacting handler the default logger, before config loads
// or any plugin can log.
func TestGenerateMainInstallsRedactingLogger(t *testing.T) {
// TestGenerateMainInstallsDiary: the first statement of run installs the
// diary console logger, before config loads or any plugin can log, and
// diary.Setup applies config/log.yaml right after compass loads it and before
// the application container exists.
func TestGenerateMainInstallsDiary(t *testing.T) {
mainSrc, err := generateMain(Manifest{Module: "example.com/app", Binary: "hello"})
if err != nil {
t.Fatal(err)
}
line := []byte("sunscreen.InstallDefault(os.Stderr)")
if n := bytes.Count(mainSrc, line); n != 1 {
t.Fatalf("InstallDefault count = %d, want 1\n%s", n, mainSrc)
bootstrap := []byte("diary.Bootstrap(os.Stderr, diary.Options{})")
setup := []byte("diary.Setup(cfg, diary.Options{})")
if n := bytes.Count(mainSrc, bootstrap); n != 1 {
t.Fatalf("Bootstrap count = %d, want 1\n%s", n, mainSrc)
}
if !bytes.Contains(mainSrc, []byte(`"git.golem15.com/golem15/summercms/modules/sunscreen"`)) {
t.Fatalf("generated main does not import sunscreen:\n%s", mainSrc)
if n := bytes.Count(mainSrc, setup); n != 1 {
t.Fatalf("Setup count = %d, want 1\n%s", n, mainSrc)
}
install := bytes.Index(mainSrc, line)
runStart := bytes.Index(mainSrc, []byte("func run(args []string, out io.Writer) error {"))
if !bytes.Contains(mainSrc, []byte(`"git.golem15.com/golem15/summercms/modules/diary"`)) {
t.Fatalf("generated main does not import diary:\n%s", mainSrc)
}
if bytes.Contains(mainSrc, []byte(`"git.golem15.com/golem15/summercms/modules/sunscreen"`)) {
t.Fatalf("generated main still imports sunscreen:\n%s", mainSrc)
}
runSig := []byte("func run(args []string, out io.Writer) error {")
runStart := bytes.Index(mainSrc, runSig)
boot := bytes.Index(mainSrc, bootstrap)
load := bytes.Index(mainSrc, []byte("compass.Load("))
if runStart < 0 || load < 0 || !(runStart < install && install < load) {
t.Fatalf("InstallDefault must be the first statement of run, before compass.Load:\n%s", mainSrc)
set := bytes.Index(mainSrc, setup)
newApp := bytes.Index(mainSrc, []byte("backpack.New("))
if runStart < 0 || load < 0 || newApp < 0 || !(runStart < boot && boot < load && load < set && set < newApp) {
t.Fatalf("want run { Bootstrap; compass.Load; Setup; backpack.New }:\n%s", mainSrc)
}
if between := bytes.TrimSpace(mainSrc[runStart+len("func run(args []string, out io.Writer) error {") : install]); len(between) != 0 {
t.Fatalf("statements before InstallDefault: %q", between)
if between := bytes.TrimSpace(mainSrc[runStart+len(runSig) : boot]); len(between) != 0 {
t.Fatalf("statements before Bootstrap: %q", between)
}
}

View File

@@ -12,10 +12,10 @@ import (
"git.golem15.com/golem15/summercms/modules/cabana"
"git.golem15.com/golem15/summercms/modules/compass"
"git.golem15.com/golem15/summercms/modules/conga"
"git.golem15.com/golem15/summercms/modules/diary"
"git.golem15.com/golem15/summercms/modules/lagoon"
"git.golem15.com/golem15/summercms/modules/pact"
"git.golem15.com/golem15/summercms/modules/party"
"git.golem15.com/golem15/summercms/modules/sunscreen"
"git.golem15.com/golem15/summercms/modules/surf"
)
@@ -27,11 +27,14 @@ func main() {
}
func run(args []string, out io.Writer) error {
sunscreen.InstallDefault(os.Stderr)
diary.Bootstrap(os.Stderr, diary.Options{})
cfg, err := compass.Load("config")
if err != nil {
return err
}
if err := diary.Setup(cfg, diary.Options{}); err != nil {
return err
}
app := backpack.New(cfg)
plugins, err := party.Activate(app, PluginIDs)
if err != nil {