Skip to content

Commit 0811d4c

Browse files
authored
logging (#2411)
* logging * ui logs * logging implementation * fix compilation errors, double declaration * expand logging coverage * imports * json compat adjustment * feat/breardon2011/opentaco-logs
1 parent 98074c3 commit 0811d4c

File tree

24 files changed

+1366
-147
lines changed

24 files changed

+1366
-147
lines changed

taco/cmd/statesman/main.go

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"context"
55
"flag"
66
"fmt"
7-
"log"
7+
"log/slog"
88
"net/http"
99
"os"
1010
"os/signal"
@@ -14,6 +14,7 @@ import (
1414
"github.com/diggerhq/digger/opentaco/internal/api"
1515
"github.com/diggerhq/digger/opentaco/internal/auth"
1616
"github.com/diggerhq/digger/opentaco/internal/domain"
17+
"github.com/diggerhq/digger/opentaco/internal/logging"
1718
"github.com/diggerhq/digger/opentaco/internal/query"
1819
"github.com/diggerhq/digger/opentaco/internal/queryfactory"
1920
"github.com/diggerhq/digger/opentaco/internal/rbac"
@@ -36,101 +37,112 @@ func main() {
3637
)
3738
flag.Parse()
3839

40+
// Initialize structured logging first (before any log statements)
41+
logging.Init("opentaco-statesman")
42+
slog.Info("Starting OpenTaco Statesman service")
43+
3944
// Load configuration from environment variables into our struct.
4045
var queryCfg query.Config
4146
err := envconfig.Process("opentaco", &queryCfg) // The prefix "TACO" will be used for all vars.
4247
if err != nil {
43-
log.Fatalf("Failed to process configuration: %v", err)
48+
slog.Error("Failed to process configuration", "error", err)
49+
os.Exit(1)
4450
}
4551

4652
// --- Initialize Stores ---
4753

4854
// Create the database index store using the dedicated factory.
4955
queryStore, err := queryfactory.NewQueryStore(queryCfg)
5056
if err != nil {
51-
log.Fatalf("Failed to initialize query backend: %v", err)
57+
slog.Error("Failed to initialize query backend", "error", err)
58+
os.Exit(1)
5259
}
5360
defer queryStore.Close()
5461

55-
log.Printf("Query backend initialized: %s", queryCfg.Backend)
62+
slog.Info("Query backend initialized", "backend", queryCfg.Backend)
5663

5764

5865
// Initialize storage
5966
var blobStore storage.UnitStore
6067
switch *storageType {
6168
case "s3":
6269
if *s3Bucket == "" {
63-
log.Printf("WARNING: S3 storage selected but bucket not provided. Falling back to in-memory storage.")
70+
slog.Warn("S3 storage selected but bucket not provided. Falling back to in-memory storage.")
6471
blobStore = storage.NewMemStore()
65-
log.Printf("Using in-memory storage")
72+
slog.Info("Using in-memory storage")
6673
break
6774
}
6875
s, err := storage.NewS3Store(context.Background(), *s3Bucket, *s3Prefix, *s3Region)
6976
if err != nil {
70-
log.Printf("WARNING: failed to initialize S3 store: %v. Falling back to in-memory storage.", err)
77+
slog.Warn("Failed to initialize S3 store. Falling back to in-memory storage.", "error", err)
7178
blobStore = storage.NewMemStore()
72-
log.Printf("Using in-memory storage")
79+
slog.Info("Using in-memory storage")
7380
} else {
7481
blobStore = s
75-
log.Printf("Using S3 storage: bucket=%s prefix=%s region=%s", *s3Bucket, *s3Prefix, *s3Region)
76-
82+
slog.Info("Using S3 storage",
83+
"bucket", *s3Bucket,
84+
"prefix", *s3Prefix,
85+
"region", *s3Region)
7786
}
7887
default:
7988
blobStore = storage.NewMemStore()
80-
log.Printf("Using in-memory storage")
89+
slog.Info("Using in-memory storage")
8190
}
8291

8392

8493
// sync units to query index
8594
existingUnits, err := queryStore.ListUnits(context.Background(), "")
8695
if err != nil {
87-
log.Printf("Warning: Failed to check for existing units: %v", err)
96+
slog.Warn("Failed to check for existing units", "error", err)
8897
}
8998

9099
if len(existingUnits) == 0 {
91-
log.Println("Query backend has no units, syncing from storage...")
100+
slog.Info("Query backend has no units, syncing from storage...")
92101
units, err := blobStore.List(context.Background(), "")
93102
if err != nil {
94-
log.Printf("Warning: Failed to list units from storage: %v", err)
103+
slog.Warn("Failed to list units from storage", "error", err)
95104
} else {
96105
for _, unit := range units {
97106
if err := queryStore.SyncEnsureUnit(context.Background(), unit.ID); err != nil {
98-
log.Printf("Warning: Failed to sync unit %s: %v", unit.ID, err)
107+
slog.Warn("Failed to sync unit", "unit_id", unit.ID, "error", err)
99108
continue
100109
}
101110

102111
if err := queryStore.SyncUnitMetadata(context.Background(), unit.ID, unit.Size, unit.Updated); err != nil {
103-
log.Printf("Warning: Failed to sync metadata for unit %s: %v", unit.ID, err)
112+
slog.Warn("Failed to sync metadata for unit", "unit_id", unit.ID, "error", err)
104113
}
105114
}
106-
log.Printf("Synced %d units from storage to database", len(units))
115+
slog.Info("Synced units from storage to database", "count", len(units))
107116
}
108117
} else {
109-
log.Printf("Query backend already has %d units, skipping sync", len(existingUnits))
118+
slog.Info("Query backend already has units, skipping sync", "count", len(existingUnits))
110119
}
111120

112121
// create repository
113122
// repository coordinates blob storage with query index internally
114123
// Get the underlying *gorm.DB from the query store
115124
db := repositories.GetDBFromQueryStore(queryStore)
116125
if db == nil {
117-
log.Fatalf("Query store does not provide GetDB method")
126+
slog.Error("Query store does not provide GetDB method")
127+
os.Exit(1)
118128
}
119129

120130
// Ensure default organization exists
121131
defaultOrgUUID, err := repositories.EnsureDefaultOrganization(context.Background(), db)
122132
if err != nil {
123-
log.Fatalf("Failed to ensure default organization: %v", err)
133+
slog.Error("Failed to ensure default organization", "error", err)
134+
os.Exit(1)
124135
}
125-
log.Printf("Default organization ensured: %s", defaultOrgUUID)
136+
slog.Info("Default organization ensured", "org_uuid", defaultOrgUUID)
126137

127138
repo := repositories.NewUnitRepository(db, blobStore)
128-
log.Println("Repository initialized (database-first with blob storage backend)")
139+
slog.Info("Repository initialized (database-first with blob storage backend)")
129140

130141
// Create RBAC Manager
131142
rbacManager, err := rbac.NewRBACManagerFromQueryStore(queryStore)
132143
if err != nil {
133-
log.Fatalf("Failed to create RBAC manager: %v", err)
144+
slog.Error("Failed to create RBAC manager", "error", err)
145+
os.Exit(1)
134146
}
135147

136148
// --- Create Domain Interfaces with Optional Authorization ---
@@ -139,7 +151,7 @@ func main() {
139151

140152
// Wrap with authorization if auth is enabled
141153
if !*authDisable {
142-
log.Println("Authorization is ENABLED. Wrapping repository with RBAC.")
154+
slog.Info("Authorization is ENABLED. Wrapping repository with RBAC.")
143155

144156
// Create bootstrap context with default org for RBAC check
145157
// During startup, we need org context to check RBAC status
@@ -148,16 +160,17 @@ func main() {
148160
// Verify RBAC manager was created successfully (fail closed for security)
149161
canInit, err := rbacManager.IsEnabled(bootstrapCtx)
150162
if err != nil {
151-
log.Fatalf("Failed to verify RBAC manager: %v", err)
163+
slog.Error("Failed to verify RBAC manager", "error", err)
164+
os.Exit(1)
152165
}
153166

154167
if !canInit {
155-
log.Println("RBAC is NOT initialized. System will operate in permissive mode until RBAC is initialized via /v1/rbac/init")
168+
slog.Info("RBAC is NOT initialized. System will operate in permissive mode until RBAC is initialized via /v1/rbac/init")
156169
}
157170

158171
fullRepo = repositories.NewAuthorizingRepository(repo, rbacManager)
159172
} else {
160-
log.Println("Authorization is DISABLED via flag. All operations allowed.")
173+
slog.Info("Authorization is DISABLED via flag. All operations allowed.")
161174
}
162175

163176
// Initialize analytics with system ID management (always create system ID)
@@ -169,13 +182,13 @@ func main() {
169182

170183
// Try to preload existing system ID and user email first
171184
if err := analytics.PreloadSystemID(ctx); err == nil {
172-
log.Printf("System ID and user email loaded from storage")
185+
slog.Info("System ID and user email loaded from storage")
173186
} else {
174187
// If preload fails, create new system ID
175188
if systemID, err := analytics.GetOrCreateSystemID(ctx); err == nil {
176-
log.Printf("System ID created: %s", systemID)
189+
slog.Info("System ID created", "system_id", systemID)
177190
} else {
178-
log.Printf("System ID not available: %v", err)
191+
slog.Warn("System ID not available", "error", err)
179192
}
180193
}
181194
analytics.SendEssential("service_startup")
@@ -187,7 +200,10 @@ func main() {
187200
// Middleware
188201
e.Use(echomiddleware.Logger())
189202
e.Use(echomiddleware.Recover())
190-
e.Use(echomiddleware.RequestID())
203+
// Use incoming X-Request-ID if provided, otherwise generate new one
204+
e.Use(echomiddleware.RequestIDWithConfig(echomiddleware.RequestIDConfig{
205+
TargetHeader: echo.HeaderXRequestID,
206+
}))
191207
e.Use(echomiddleware.Gzip())
192208
e.Use(echomiddleware.Secure())
193209
e.Use(echomiddleware.CORS())
@@ -196,7 +212,8 @@ func main() {
196212
// Create a signer for JWTs (this may need to be configured from env vars)
197213
signer, err := auth.NewSignerFromEnv()
198214
if err != nil {
199-
log.Fatalf("Failed to initialize JWT signer: %v", err)
215+
slog.Error("Failed to initialize JWT signer", "error", err)
216+
os.Exit(1)
200217
}
201218

202219
// Register routes with interface-based dependencies
@@ -213,11 +230,12 @@ func main() {
213230
// Start server
214231
go func() {
215232
addr := fmt.Sprintf(":%s", *port)
216-
log.Printf("Starting OpenTaco service on %s", addr)
233+
slog.Info("Starting OpenTaco service", "address", addr, "port", *port)
217234
analytics.SendEssential("server_started")
218235
if err := e.Start(addr); err != nil && err != http.ErrServerClosed {
219236
analytics.SendEssential("server_startup_failed")
220-
log.Fatalf("Server startup failed: %v", err)
237+
slog.Error("Server startup failed", "error", err)
238+
os.Exit(1)
221239
}
222240
}()
223241

@@ -228,15 +246,17 @@ func main() {
228246

229247
// Graceful shutdown
230248
analytics.SendEssential("server_shutdown_initiated")
249+
slog.Info("Shutting down server gracefully...")
231250
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
232251
defer cancel()
233252

234253
if err := e.Shutdown(shutdownCtx); err != nil {
235254
analytics.SendEssential("server_shutdown_failed")
236-
log.Fatalf("Server shutdown failed: %v", err)
255+
slog.Error("Server shutdown failed", "error", err)
256+
os.Exit(1)
237257
}
238258

239259
analytics.SendEssential("server_shutdown_complete")
240-
log.Println("Server shutdown complete")
260+
slog.Info("Server shutdown complete")
241261
}
242262

taco/cmd/token_service/main.go

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import (
44
"context"
55
"flag"
66
"fmt"
7-
"log"
7+
"log/slog"
88
"net/http"
99
"os"
1010
"os/signal"
1111
"time"
1212

1313
"github.com/diggerhq/digger/opentaco/cmd/token_service/query"
1414
querytypes "github.com/diggerhq/digger/opentaco/cmd/token_service/query/types"
15+
"github.com/diggerhq/digger/opentaco/internal/logging"
1516
"github.com/diggerhq/digger/opentaco/internal/token_service"
1617
"github.com/kelseyhightower/envconfig"
1718
"github.com/labstack/echo/v4"
@@ -25,40 +26,48 @@ func main() {
2526
)
2627
flag.Parse()
2728

29+
// Initialize structured logging first (before any log statements)
30+
logging.Init("opentaco-token-service")
31+
slog.Info("Starting OpenTaco Token Service")
32+
2833
// Load configuration from environment variables with "opentaco_token" prefix
2934
var dbCfg query.Config
3035
err := envconfig.Process("opentaco_token", &dbCfg)
3136
if err != nil {
32-
log.Fatalf("Failed to process token service database configuration: %v", err)
37+
slog.Error("Failed to process token service database configuration", "error", err)
38+
os.Exit(1)
3339
}
3440

3541
// --- Initialize Token Service Database ---
3642

3743
// Create the database connection for token service
3844
db, err := query.NewDB(dbCfg)
3945
if err != nil {
40-
log.Fatalf("Failed to initialize token service database: %v", err)
46+
slog.Error("Failed to initialize token service database", "error", err)
47+
os.Exit(1)
4148
}
4249

4350
sqlDB, err := db.DB()
4451
if err != nil {
45-
log.Fatalf("Failed to get underlying sql.DB: %v", err)
52+
slog.Error("Failed to get underlying sql.DB", "error", err)
53+
os.Exit(1)
4654
}
4755
defer sqlDB.Close()
4856

49-
log.Printf("Token service database initialized: %s", dbCfg.Backend)
57+
slog.Info("Token service database initialized", "backend", dbCfg.Backend)
5058

5159
// Auto-migrate token models (ensures schema exists)
5260
// Note: In Docker, migrations are applied via Atlas in entrypoint.sh
5361
// This AutoMigrate is primarily for local development convenience
5462
if err := db.AutoMigrate(querytypes.TokenModels...); err != nil {
55-
log.Fatalf("Failed to auto-migrate token models: %v", err)
63+
slog.Error("Failed to auto-migrate token models", "error", err)
64+
os.Exit(1)
5665
}
57-
log.Println("Token service database schema verified")
66+
slog.Info("Token service database schema verified")
5867

5968
// Create token repository
6069
tokenRepo := token_service.NewTokenRepository(db)
61-
log.Println("Token repository initialized")
70+
slog.Info("Token repository initialized")
6271

6372
// Create Echo instance
6473
e := echo.New()
@@ -67,7 +76,10 @@ func main() {
6776
// Middleware
6877
e.Use(echomiddleware.Logger())
6978
e.Use(echomiddleware.Recover())
70-
e.Use(echomiddleware.RequestID())
79+
// Use incoming X-Request-ID if provided, otherwise generate new one
80+
e.Use(echomiddleware.RequestIDWithConfig(echomiddleware.RequestIDConfig{
81+
TargetHeader: echo.HeaderXRequestID,
82+
}))
7183
e.Use(echomiddleware.Gzip())
7284
e.Use(echomiddleware.Secure())
7385
e.Use(echomiddleware.CORS())
@@ -78,9 +90,10 @@ func main() {
7890
// Start server
7991
go func() {
8092
addr := fmt.Sprintf(":%s", *port)
81-
log.Printf("Starting Token Service on %s", addr)
93+
slog.Info("Starting Token Service", "address", addr, "port", *port)
8294
if err := e.Start(addr); err != nil && err != http.ErrServerClosed {
83-
log.Fatalf("Server startup failed: %v", err)
95+
slog.Error("Server startup failed", "error", err)
96+
os.Exit(1)
8497
}
8598
}()
8699

@@ -90,14 +103,15 @@ func main() {
90103
<-quit
91104

92105
// Graceful shutdown
93-
log.Println("Shutting down server...")
106+
slog.Info("Shutting down server gracefully...")
94107
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
95108
defer cancel()
96109

97110
if err := e.Shutdown(shutdownCtx); err != nil {
98-
log.Fatalf("Server shutdown failed: %v", err)
111+
slog.Error("Server shutdown failed", "error", err)
112+
os.Exit(1)
99113
}
100114

101-
log.Println("Server shutdown complete")
115+
slog.Info("Server shutdown complete")
102116
}
103117

0 commit comments

Comments
 (0)