feat(T1.2): implement structured logging and Prometheus metrics

- Add internal/logging package with zap-based structured JSON logging
- Support development (colored) and production (JSON) modes via ENVIRONMENT env var
- Add logging helpers: Info(), Error(), Warn(), Debug(), Fatal()
- Add field helpers: String(), Int(), Int64(), Err()
- Add internal/metrics package with 16 comprehensive Prometheus metrics
- Track workflows: starts, completions, duration by type/status
- Track activities: starts, completions, duration, retries by type
- Track LLM calls: total calls and latency by model
- Track git operations: total and duration by operation type
- Track judge decisions: decisions by type
- Track Temporal errors: connection errors by type
- Track cache efficiency: hits and misses by cache type
- Track tasks in progress: gauge metric by task type
- Metrics exported on /metrics endpoint (Prometheus text format)
- Integrate structured logging in cmd/worker and cmd/starter
- Replace all log.Printf/log.Fatalf with structured logging
- Add /metrics endpoint to health check server
- 8/8 logging tests passing, 13/13 metrics tests passing
- All verification criteria met

Dependencies added:
- go.uber.org/zap v1.28.0 (structured logging)
- github.com/prometheus/client_golang v1.24.1 (metrics export)

Closes T1.2
This commit is contained in:
Test
2026-08-23 16:33:49 -07:00
parent 90fcd6a9df
commit 59a1eeed85
11 changed files with 808 additions and 66 deletions
+15 -8
View File
@@ -2,7 +2,6 @@ package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
@@ -15,14 +14,21 @@ import (
"github.com/rockliang/poimen/workflows/action"
"github.com/rockliang/poimen/workflows/internal/config"
"github.com/rockliang/poimen/workflows/internal/health"
"github.com/rockliang/poimen/workflows/internal/logging"
"github.com/rockliang/poimen/workflows/statemachine"
)
func main() {
// Initialize structured logging
if err := logging.InitLogger(); err != nil {
log.Fatalf("failed to initialize logger: %v", err)
}
defer logging.Sync()
// Load configuration
cfg, err := config.LoadConfig()
if err != nil {
log.Fatalf("failed to load config: %v", err)
logging.Fatal("failed to load config", logging.Err(err))
}
// Connect to Temporal
@@ -31,14 +37,14 @@ func main() {
Namespace: cfg.Temporal.Namespace,
})
if err != nil {
log.Fatalf("failed to connect to temporal: %v", err)
logging.Fatal("failed to connect to temporal", logging.Err(err))
}
defer c.Close()
// Create worker
w := worker.New(c, "poimen-taskqueue", worker.Options{})
if w == nil {
log.Fatalf("failed to create worker")
logging.Fatal("failed to create worker")
}
// Register all workflows
@@ -90,7 +96,7 @@ func main() {
// Run worker in a goroutine
workerErrChan := make(chan error, 1)
go func() {
fmt.Println("Starting worker on queue 'poimen-taskqueue'...")
logging.Info("starting worker on queue", logging.String("queue", "poimen-taskqueue"))
if err := w.Run(worker.InterruptCh()); err != nil {
workerErrChan <- err
}
@@ -99,16 +105,17 @@ func main() {
// Wait for either worker error or signal
select {
case err := <-workerErrChan:
log.Fatalf("worker failed: %v", err)
logging.Fatal("worker failed", logging.Err(err))
case sig := <-sigChan:
log.Printf("received signal: %v, shutting down gracefully", sig)
logging.Info("received signal", logging.String("signal", sig.String()))
w.Stop()
// Shutdown health check server
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := healthServer.Shutdown(ctx); err != nil {
log.Printf("health check server shutdown error: %v", err)
logging.Warn("health check server shutdown error", logging.Err(err))
}
logging.Info("worker shutdown complete")
}
}