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:
+15
-5
@@ -11,6 +11,7 @@ import (
|
||||
"go.temporal.io/sdk/client"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -27,37 +28,45 @@ func main() {
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
// Initialize structured logging
|
||||
if err := logging.InitLogger(); err != nil {
|
||||
log.Fatalf("failed to initialize logger: %v", err)
|
||||
}
|
||||
defer logging.Sync()
|
||||
|
||||
// Load configuration first
|
||||
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
|
||||
logging.Info("connecting to Temporal", logging.String("hostPort", cfg.Temporal.HostPort), logging.String("namespace", cfg.Temporal.Namespace))
|
||||
c, err := client.Dial(client.Options{
|
||||
HostPort: cfg.Temporal.HostPort,
|
||||
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()
|
||||
|
||||
// If health check requested, do it and exit
|
||||
if *healthCheck {
|
||||
logging.Info("running health check")
|
||||
healthChecker := health.NewChecker(c)
|
||||
report := healthChecker.Check(context.Background())
|
||||
jsonReport, _ := report.ToJSON()
|
||||
fmt.Println(string(jsonReport))
|
||||
if report.Status != health.StatusHealthy {
|
||||
log.Fatalf("health check failed")
|
||||
logging.Fatal("health check failed")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Validate required flags for workflow start
|
||||
if *repoPath == "" || *remoteURL == "" {
|
||||
log.Fatalf("--repo and --remote flags are required")
|
||||
logging.Fatal("--repo and --remote flags are required")
|
||||
}
|
||||
|
||||
|
||||
@@ -102,12 +111,13 @@ func main() {
|
||||
|
||||
// Start workflow
|
||||
workflowID := "orch-" + strings.ReplaceAll(*repoPath, "/", "-")
|
||||
logging.Info("starting orchestrator workflow", logging.String("workflowID", workflowID), logging.String("repo", *repoPath))
|
||||
run, err := c.ExecuteWorkflow(context.Background(), client.StartWorkflowOptions{
|
||||
ID: workflowID,
|
||||
TaskQueue: "poimen-taskqueue",
|
||||
}, statemachine.OrchestratorWorkflow, input)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to start workflow: %v", err)
|
||||
logging.Fatal("failed to start workflow", logging.Err(err))
|
||||
}
|
||||
|
||||
fmt.Printf("\n=== Workflow Started ===\n")
|
||||
|
||||
+15
-8
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user