Skip to content

Commit 20d5dc8

Browse files
jonradoffclaude
andcommitted
Add optional DataDog integration for telemetry and syslog forwarding
Forwards telemetry events as custom metrics (POST /api/v2/series) and critical/high syslog entries as DataDog events (POST /api/v1/events) via REST API — no DataDog Agent required. Completely inert when DATADOG_API_KEY is not configured. Includes health check validation, API call counting, and async buffered submission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6b6f6cc commit 20d5dc8

11 files changed

Lines changed: 447 additions & 6 deletions

File tree

backend/cmd/server/main.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
stripeservice "lastsaas/internal/stripe"
2828
"lastsaas/internal/syslog"
2929
"lastsaas/internal/telemetry"
30+
"lastsaas/internal/datadog"
3031
"lastsaas/internal/version"
3132
"lastsaas/internal/webhooks"
3233

@@ -140,6 +141,22 @@ func main() {
140141

141142
// Initialize system logger
142143
sysLogger := syslog.New(database, cfgStore.Get)
144+
145+
// Initialize DataDog integration (optional)
146+
var ddClient *datadog.Client
147+
if cfg.DataDog.APIKey != "" {
148+
site := cfg.DataDog.Site
149+
if site == "" {
150+
site = "us5.datadoghq.com"
151+
}
152+
ddClient = datadog.New(cfg.DataDog.APIKey, site, cfg.Environment, cfg.App.Name)
153+
defer ddClient.Stop()
154+
sysLogger.SetOnLog(ddClient.TrackSyslogEntry)
155+
slog.Info("DataDog integration configured", "site", site)
156+
} else {
157+
slog.Warn("DataDog integration not configured", "reason", "missing API key")
158+
}
159+
143160
sysLogger.Critical(context.Background(), fmt.Sprintf("System startup: LastSaaS v%s", version.Current))
144161

145162
// Initialize services
@@ -276,13 +293,21 @@ func main() {
276293
} else {
277294
healthService.RegisterIntegration("saml_sso", nil)
278295
}
296+
if ddClient != nil {
297+
healthService.RegisterIntegration("datadog", health.NewDataDogChecker(ddClient))
298+
} else {
299+
healthService.RegisterIntegration("datadog", nil)
300+
}
279301

280302
healthService.Start()
281303
defer healthService.Stop()
282304

283305
// Initialize telemetry service
284306
telemetrySvc := telemetry.New(database)
285307
defer telemetrySvc.Stop()
308+
if ddClient != nil {
309+
telemetrySvc.SetOnTrack(ddClient.TrackTelemetryEvent)
310+
}
286311

287312
// Initialize handlers
288313
bootstrapHandler := handlers.NewBootstrapHandler(database)

backend/config/dev.example.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ stripe:
3535

3636
webhooks:
3737
encryption_key: ${WEBHOOK_ENCRYPTION_KEY}
38+
39+
datadog:
40+
api_key: ${DATADOG_API_KEY}
41+
site: ${DATADOG_SITE:us5.datadoghq.com}

backend/config/prod.example.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ stripe:
3636

3737
webhooks:
3838
encryption_key: ${WEBHOOK_ENCRYPTION_KEY}
39+
40+
datadog:
41+
api_key: ${DATADOG_API_KEY}
42+
site: ${DATADOG_SITE:us5.datadoghq.com}

backend/internal/apicounter/counter.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import "sync/atomic"
77
// snapshot/reset by the health collector every 60 seconds.
88

99
var (
10-
StripeAPICalls atomic.Int64
11-
ResendEmails atomic.Int64
10+
StripeAPICalls atomic.Int64
11+
ResendEmails atomic.Int64
12+
DataDogAPICalls atomic.Int64
1213
)

backend/internal/config/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type Config struct {
2222
Stripe StripeConfig `yaml:"stripe"`
2323
WebAuthn WebAuthnConfig `yaml:"webauthn"`
2424
Webhooks WebhooksConfig `yaml:"webhooks"`
25+
DataDog DataDogConfig `yaml:"datadog"`
2526
}
2627

2728
type WebhooksConfig struct {
@@ -84,6 +85,11 @@ type StripeConfig struct {
8485
WebhookSecret string `yaml:"webhook_secret"`
8586
}
8687

88+
type DataDogConfig struct {
89+
APIKey string `yaml:"api_key"`
90+
Site string `yaml:"site"` // e.g. "us5.datadoghq.com"
91+
}
92+
8793
// LoadEnvFile loads a .env file into the process environment if it exists.
8894
// It searches the current directory and up to 3 parent directories.
8995
func LoadEnvFile() {

0 commit comments

Comments
 (0)