template/cmd/app/main.go
djmil 4f55fcbef5 build stamping
- store build metadata (version, commit, date)
- multi-binary build: `make build` builds all packages from ./cmd
- devcontainer adds ./bin to the PATH by default
2026-06-13 20:32:24 +00:00

86 lines
2.3 KiB
Go

// main is the composition root for the application.
// It parses config, wires dependencies into an app struct, and delegates.
// Implementation details lives in internal/; cmd/ should be kept thin by
// deliberately operating only with high level concepts.
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"gitea.djmil.dev/go/template/internal/buildinfo"
"gitea.djmil.dev/go/template/internal/greeter"
"gitea.djmil.dev/go/template/pkg/logger"
"gitea.djmil.dev/go/template/pkg/result"
)
// app holds all wired dependencies for the lifetime of the process.
type app struct {
cfg *Config
log *logger.Logger
greeter *greeter.Service
}
func newApp(cfg *Config) *app {
// Open the debug writer if requested. The OS closes it on exit.
var debugOut io.Writer
if cfg.Logger.LogDump != "" {
debugOut = result.Of(os.Create(cfg.Logger.LogDump)).Expect("enable logs dump") // #nosec G304 — CLI flag
}
log := logger.NewCLI(cfg.Logger.Level, debugOut).Expect("create logger")
log.Debug("config", "port", cfg.App.Port, "level", cfg.Logger.Level, "env", cfg.App.Env)
if cfg.App.Env == "dev" {
log.Warn("dev mode — not for production")
}
return &app{
cfg: cfg,
log: log,
greeter: greeter.New(log),
}
}
func main() {
conf := parseArgs()
if conf.Version {
fmt.Println(buildinfo.String())
return
}
app := newApp(conf)
app.log.WithFields(map[string]any{
"app": filepath.Base(os.Args[0]),
"env": conf.App.Env,
}).Info("starting up")
if err := result.Run(app.run); err != nil {
fmt.Fprintf(os.Stderr, "[failed] %v\n", err)
if stack := result.StackTrace(err); stack != "" {
fmt.Fprintf(os.Stderr, "%s\n", stack)
}
os.Exit(1)
}
}
func (a *app) run() {
// Warm up the greeter with a few names before the real call.
// In debug mode this produces repeated identical debug lines from the greeter,
// demonstrating how the deduplication counter collapses them in-place.
for _, name := range []string{"Alice", "Bob", "Carol", "Dave"} {
a.greeter.Greet(name)
}
a.showGreeting(a.cfg.Greeter.Name)
fmt.Printf("TODO: implement listening on port %d\n", a.cfg.App.Port)
}
func (a *app) showGreeting(name string) {
msg := a.greeter.Greet(name).Expect("greeting")
a.log.WithField("message", msg).Info("greeting complete")
fmt.Println(msg)
}