Skip to content

Commit 1d895f0

Browse files
authored
feat: wire structured logger (slog) throughout detection pipeline (#16)
## What and Why ? Address pending `TODO` related to structured logging - Replaces `log.Printf` calls with structured logging using Go's `log/slog` package. Adds logger fields to detector, inventory, and EOL provider components with consistent nil-safe defaults. ### Changes - **Detectors**: Add `logger` field to EKS and Aurora detectors - **Inventory**: Add `logger` parameter to Wiz inventory sources (EKS, Aurora, ElastiCache) and parsing helpers - **EOL Provider**: Add `logger` field to endoflife.date provider with warning logs for skipped cycles - **Main**: Initialize JSON logger at startup and wire through all components - **Tests**: Update all test constructors to pass `nil` logger (uses `slog.Default()`) ### Benefits - Structured JSON logging for production observability - Context-aware log messages with typed fields - Consistent logging pattern across codebase - All TODO comments for logging addressed All tests pass ✅
1 parent 484b10a commit 1d895f0

File tree

20 files changed

+175
-55
lines changed

20 files changed

+175
-55
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Binaries
22
bin/
3+
server
34
*.exe
45
*.dll
56
*.so

ARCHITECTURE.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,21 @@ make run-locally # One-shot
624624
### Monitoring
625625

626626
- **Metrics**: Expose Prometheus metrics from gRPC service
627-
- **Logs**: Structured JSON logging
627+
- **Logs**: Structured JSON logging via `log/slog`
628+
- Machine-readable JSON format for log aggregation tools (Datadog, Splunk, CloudWatch Insights)
629+
- Context-aware logging with typed fields for queryable log data
630+
- Configurable log levels (Info/Debug via `--verbose` flag)
631+
- All components (detectors, inventory sources, EOL providers) use structured logging
632+
- Example log entry:
633+
```json
634+
{
635+
"time": "2024-01-15T10:30:45Z",
636+
"level": "WARN",
637+
"msg": "failed to parse resource from CSV row",
638+
"row_number": 42,
639+
"error": "missing ARN"
640+
}
641+
```
628642
- **Alerts**: Based on RED/YELLOW finding counts
629643
- **Dashboards**: Query gRPC API for real-time data
630644

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ Version Guard is configured via environment variables or CLI flags:
205205
| `TAG_APP_KEYS` | Comma-separated AWS tag keys for app/service | `app,application,service` |
206206
| `TAG_ENV_KEYS` | Comma-separated AWS tag keys for environment | `environment,env` |
207207
| `TAG_BRAND_KEYS` | Comma-separated AWS tag keys for brand/business unit | `brand` |
208+
| `--verbose` / `-v` | Enable debug-level logging | `false` |
208209

209210
**Customizing AWS Tag Keys:**
210211

@@ -220,6 +221,36 @@ export TAG_APP_KEYS="team,squad,application"
220221

221222
The tag keys are tried in order — the first matching tag wins.
222223

224+
**Logging:**
225+
226+
Version Guard uses structured JSON logging via Go's `log/slog` package for production observability:
227+
228+
```bash
229+
# Run with debug-level logging
230+
./bin/version-guard --verbose
231+
232+
# Production mode (info-level logging only)
233+
./bin/version-guard
234+
```
235+
236+
Logs are output in JSON format for easy parsing by log aggregation tools (Datadog, Splunk, CloudWatch Insights):
237+
238+
```json
239+
{
240+
"time": "2024-01-15T10:30:45Z",
241+
"level": "WARN",
242+
"msg": "failed to detect drift for resource",
243+
"resource_id": "arn:aws:rds:us-west-2:123456789012:cluster:my-db",
244+
"error": "version not found in EOL database"
245+
}
246+
```
247+
248+
Benefits:
249+
- Machine-readable structured data with typed fields
250+
- Context-aware logging with trace IDs
251+
- Queryable logs (e.g., filter by `resource_id` or `error`)
252+
- Integrates seamlessly with observability platforms
253+
223254
See `./bin/version-guard --help` for all options.
224255

225256
## 🎨 Classification Policy

cmd/server/main.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"context"
55
"fmt"
6+
"log/slog"
67
"os"
78
"os/signal"
89
"strings"
@@ -99,6 +100,16 @@ func (s *ServerCLI) buildTagConfig() *wiz.TagConfig {
99100
}
100101

101102
func (s *ServerCLI) Run(_ *kong.Context) error {
103+
// Initialize structured logger
104+
logLevel := slog.LevelInfo
105+
if s.Verbose {
106+
logLevel = slog.LevelDebug
107+
}
108+
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
109+
Level: logLevel,
110+
}))
111+
slog.SetDefault(logger)
112+
102113
fmt.Println("Starting Version Guard Detector Service (Open Source)")
103114
fmt.Printf(" Version: %s\n", version)
104115
fmt.Printf(" Temporal Endpoint: %s\n", s.TemporalEndpoint)
@@ -184,17 +195,17 @@ func (s *ServerCLI) Run(_ *kong.Context) error {
184195
wizClient := wiz.NewClient(wizHTTPClient, time.Duration(s.WizCacheTTLHours)*time.Hour)
185196

186197
if s.WizAuroraReportID != "" {
187-
invSources[types.ResourceTypeAurora] = wiz.NewAuroraInventorySource(wizClient, s.WizAuroraReportID).
198+
invSources[types.ResourceTypeAurora] = wiz.NewAuroraInventorySource(wizClient, s.WizAuroraReportID, logger).
188199
WithTagConfig(tagConfig)
189200
fmt.Println("✓ Aurora inventory source configured (Wiz)")
190201
}
191202
if s.WizElastiCacheReportID != "" {
192-
invSources[types.ResourceTypeElastiCache] = wiz.NewElastiCacheInventorySource(wizClient, s.WizElastiCacheReportID).
203+
invSources[types.ResourceTypeElastiCache] = wiz.NewElastiCacheInventorySource(wizClient, s.WizElastiCacheReportID, logger).
193204
WithTagConfig(tagConfig)
194205
fmt.Println("✓ ElastiCache inventory source configured (Wiz)")
195206
}
196207
if s.WizEKSReportID != "" {
197-
invSources[types.ResourceTypeEKS] = wiz.NewEKSInventorySource(wizClient, s.WizEKSReportID).
208+
invSources[types.ResourceTypeEKS] = wiz.NewEKSInventorySource(wizClient, s.WizEKSReportID, logger).
198209
WithTagConfig(tagConfig)
199210
fmt.Println("✓ EKS inventory source configured (Wiz)")
200211
}
@@ -240,15 +251,15 @@ func (s *ServerCLI) Run(_ *kong.Context) error {
240251
cacheTTL := 24 * time.Hour
241252

242253
// Aurora EOL provider (using endoflife.date for PostgreSQL versions)
243-
eolProviders[types.ResourceTypeAurora] = eolendoflife.NewProvider(eolHTTPClient, cacheTTL)
254+
eolProviders[types.ResourceTypeAurora] = eolendoflife.NewProvider(eolHTTPClient, cacheTTL, logger)
244255
fmt.Println("✓ Aurora EOL provider configured (endoflife.date API)")
245256

246257
// EKS EOL provider (using endoflife.date for Kubernetes versions)
247-
eolProviders[types.ResourceTypeEKS] = eolendoflife.NewProvider(eolHTTPClient, cacheTTL)
258+
eolProviders[types.ResourceTypeEKS] = eolendoflife.NewProvider(eolHTTPClient, cacheTTL, logger)
248259
fmt.Println("✓ EKS EOL provider configured (endoflife.date API)")
249260

250261
// ElastiCache EOL provider
251-
eolProviders[types.ResourceTypeElastiCache] = eolendoflife.NewProvider(eolHTTPClient, cacheTTL)
262+
eolProviders[types.ResourceTypeElastiCache] = eolendoflife.NewProvider(eolHTTPClient, cacheTTL, logger)
252263
fmt.Println("✓ ElastiCache EOL provider configured (endoflife.date API)")
253264

254265
// Initialize policy engine
@@ -261,6 +272,7 @@ func (s *ServerCLI) Run(_ *kong.Context) error {
261272
invSources[types.ResourceTypeAurora],
262273
eolProviders[types.ResourceTypeAurora],
263274
policyEngine,
275+
logger,
264276
)
265277
fmt.Println("✓ Aurora detector initialized")
266278
}
@@ -271,6 +283,7 @@ func (s *ServerCLI) Run(_ *kong.Context) error {
271283
invSources[types.ResourceTypeEKS],
272284
eolProviders[types.ResourceTypeEKS],
273285
policyEngine,
286+
logger,
274287
)
275288
fmt.Println("✓ EKS detector initialized")
276289
}

pkg/detector/aurora/detector.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package aurora
33
import (
44
"context"
55
"fmt"
6+
"log/slog"
67
"time"
78

89
"github.com/pkg/errors"
@@ -18,18 +19,24 @@ type Detector struct {
1819
inventory inventory.InventorySource
1920
eol eol.Provider
2021
policy policy.VersionPolicy
22+
logger *slog.Logger
2123
}
2224

2325
// NewDetector creates a new Aurora detector
2426
func NewDetector(
2527
inventory inventory.InventorySource,
2628
eol eol.Provider,
2729
policy policy.VersionPolicy,
30+
logger *slog.Logger,
2831
) *Detector {
32+
if logger == nil {
33+
logger = slog.Default()
34+
}
2935
return &Detector{
3036
inventory: inventory,
3137
eol: eol,
3238
policy: policy,
39+
logger: logger,
3340
}
3441
}
3542

pkg/detector/aurora/detector_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func TestDetector_Detect_EOLVersion(t *testing.T) {
4949
mockInventory,
5050
mockEOL,
5151
policy.NewDefaultPolicy(),
52+
nil, // logger
5253
)
5354

5455
// Run detection
@@ -119,6 +120,7 @@ func TestDetector_Detect_CurrentVersion(t *testing.T) {
119120
mockInventory,
120121
mockEOL,
121122
policy.NewDefaultPolicy(),
123+
nil, // logger
122124
)
123125

124126
// Run detection
@@ -176,6 +178,7 @@ func TestDetector_Detect_ExtendedSupport(t *testing.T) {
176178
mockInventory,
177179
mockEOL,
178180
policy.NewDefaultPolicy(),
181+
nil, // logger
179182
)
180183

181184
// Run detection
@@ -257,6 +260,7 @@ func TestDetector_Detect_MultipleResources(t *testing.T) {
257260
mockInventory,
258261
mockEOL,
259262
policy.NewDefaultPolicy(),
263+
nil, // logger
260264
)
261265

262266
// Run detection
@@ -311,6 +315,7 @@ func TestDetector_Detect_EmptyInventory(t *testing.T) {
311315
mockInventory,
312316
mockEOL,
313317
policy.NewDefaultPolicy(),
318+
nil, // logger
314319
)
315320

316321
// Run detection
@@ -327,7 +332,7 @@ func TestDetector_Detect_EmptyInventory(t *testing.T) {
327332
}
328333

329334
func TestDetector_Name(t *testing.T) {
330-
detector := NewDetector(nil, nil, nil)
335+
detector := NewDetector(nil, nil, nil, nil)
331336

332337
name := detector.Name()
333338
expected := "aurora-detector"
@@ -338,7 +343,7 @@ func TestDetector_Name(t *testing.T) {
338343
}
339344

340345
func TestDetector_ResourceType(t *testing.T) {
341-
detector := NewDetector(nil, nil, nil)
346+
detector := NewDetector(nil, nil, nil, nil)
342347

343348
resourceType := detector.ResourceType()
344349

pkg/detector/aurora/integration_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ func TestFullFlow_MultipleResourcesWithDifferentStatuses(t *testing.T) {
154154
mockInventory,
155155
mockEOL,
156156
policy.NewDefaultPolicy(),
157+
nil, // logger
157158
)
158159

159160
// Execute: Run the full detection flow
@@ -368,7 +369,7 @@ func TestFullFlow_SummaryStatistics(t *testing.T) {
368369
},
369370
}
370371

371-
detector := NewDetector(mockInventory, mockEOL, policy.NewDefaultPolicy())
372+
detector := NewDetector(mockInventory, mockEOL, policy.NewDefaultPolicy(), nil)
372373
findings, err := detector.Detect(context.Background())
373374

374375
if err != nil {

pkg/detector/eks/detector.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package eks
22

33
import (
44
"context"
5-
"log"
5+
"log/slog"
66
"time"
77

88
"github.com/pkg/errors"
@@ -18,18 +18,24 @@ type Detector struct {
1818
inventory inventory.InventorySource
1919
eol eol.Provider
2020
policy policy.VersionPolicy
21+
logger *slog.Logger
2122
}
2223

2324
// NewDetector creates a new EKS detector
2425
func NewDetector(
2526
inventory inventory.InventorySource,
2627
eol eol.Provider,
2728
policy policy.VersionPolicy,
29+
logger *slog.Logger,
2830
) *Detector {
31+
if logger == nil {
32+
logger = slog.Default()
33+
}
2934
return &Detector{
3035
inventory: inventory,
3136
eol: eol,
3237
policy: policy,
38+
logger: logger,
3339
}
3440
}
3541

@@ -62,8 +68,9 @@ func (d *Detector) Detect(ctx context.Context) ([]*types.Finding, error) {
6268
finding, err := d.detectResource(ctx, resource)
6369
if err != nil {
6470
// Log error but continue with other resources
65-
// TODO: wire through proper structured logger (e.g., *slog.Logger)
66-
log.Printf("WARN: failed to detect drift for %s: %v", resource.ID, err)
71+
d.logger.WarnContext(ctx, "failed to detect drift for resource",
72+
"resource_id", resource.ID,
73+
"error", err)
6774
continue
6875
}
6976

pkg/detector/eks/detector_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func TestDetector_Detect_EOLVersion(t *testing.T) {
4949
mockInventory,
5050
mockEOL,
5151
policy.NewDefaultPolicy(),
52+
nil, // logger
5253
)
5354

5455
// Run detection
@@ -119,6 +120,7 @@ func TestDetector_Detect_CurrentVersion(t *testing.T) {
119120
mockInventory,
120121
mockEOL,
121122
policy.NewDefaultPolicy(),
123+
nil, // logger
122124
)
123125

124126
// Run detection
@@ -176,6 +178,7 @@ func TestDetector_Detect_ExtendedSupport(t *testing.T) {
176178
mockInventory,
177179
mockEOL,
178180
policy.NewDefaultPolicy(),
181+
nil, // logger
179182
)
180183

181184
// Run detection
@@ -257,6 +260,7 @@ func TestDetector_Detect_MultipleResources(t *testing.T) {
257260
mockInventory,
258261
mockEOL,
259262
policy.NewDefaultPolicy(),
263+
nil, // logger
260264
)
261265

262266
// Run detection
@@ -311,6 +315,7 @@ func TestDetector_Detect_EmptyInventory(t *testing.T) {
311315
mockInventory,
312316
mockEOL,
313317
policy.NewDefaultPolicy(),
318+
nil, // logger
314319
)
315320

316321
// Run detection
@@ -327,7 +332,7 @@ func TestDetector_Detect_EmptyInventory(t *testing.T) {
327332
}
328333

329334
func TestDetector_Name(t *testing.T) {
330-
detector := NewDetector(nil, nil, nil)
335+
detector := NewDetector(nil, nil, nil, nil)
331336

332337
name := detector.Name()
333338
expected := "eks-detector"
@@ -338,7 +343,7 @@ func TestDetector_Name(t *testing.T) {
338343
}
339344

340345
func TestDetector_ResourceType(t *testing.T) {
341-
detector := NewDetector(nil, nil, nil)
346+
detector := NewDetector(nil, nil, nil, nil)
342347

343348
resourceType := detector.ResourceType()
344349

pkg/detector/eks/integration_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ func TestFullFlow_MultipleResourcesWithDifferentStatuses(t *testing.T) {
146146
mockInventory,
147147
mockEOL,
148148
policy.NewDefaultPolicy(),
149+
nil, // logger
149150
)
150151

151152
// Execute: Run detection

0 commit comments

Comments
 (0)