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
0 commit comments