// example-grpc-producer demonstrates how a peer Go service publishes // alerts to ingestd via the gRPC StreamAlerts bidi-stream. // // Usage: // // BA_API_KEY="company:source:secret" \ // go run ./cmd/example-grpc-producer \ // --addr localhost:9090 \ // --rate 100 \ // --duration 30s // // This is the reference implementation for the "How to publish to broad-announce" // story documented in SPEC ยง19. package main import ( "context" "flag" "fmt" "log/slog" "math/rand" "os" "sync/atomic" "time" grpcclient "git3.techno-world.net/lrosales/broad-announce/internal/grpcclient" pbv1 "git3.techno-world.net/lrosales/broad-announce/gen/go/broadannounce/v1" ) func main() { addr := flag.String("addr", "localhost:9090", "ingestd gRPC address") rate := flag.Int("rate", 100, "alerts per second") duration := flag.Duration("duration", 30*time.Second, "how long to run") flag.Parse() apiKey := os.Getenv("BA_API_KEY") if apiKey == "" { fmt.Fprintln(os.Stderr, "BA_API_KEY env var is required") os.Exit(1) } logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelInfo, })) ctx, cancel := context.WithTimeout(context.Background(), *duration) defer cancel() // Connect to ingestd. c, err := grpcclient.New(*addr, grpcclient.WithAPIKey(apiKey), grpcclient.WithInsecure(), // remove in production (use TLS/mTLS) ) if err != nil { logger.Error("grpcclient.New", "addr", *addr, "err", err) os.Exit(1) } defer c.Close() // Open a stream. stream, err := c.Stream(ctx) if err != nil { logger.Error("Client.Stream", "err", err) os.Exit(1) } defer stream.CloseSend() var sent int64 // Producer goroutine: generates alerts at --rate. alertCh := make(chan *pbv1.Alert, *rate*2) go func() { ticker := time.NewTicker(time.Second / time.Duration(*rate)) defer ticker.Stop() defer close(alertCh) i := 0 for { select { case <-ctx.Done(): return case <-ticker.C: alertCh <- randomAlert(i) i++ } } }() // Send loop: pulls from alertCh and sends to ingestd. go func() { for alert := range alertCh { if err := stream.Send(ctx, alert); err != nil { logger.Warn("stream.Send", "err", err) continue } atomic.AddInt64(&sent, 1) if sent%100 == 0 { logger.Info("sent", "count", sent) } } }() // Recv loop: reads Acks and logs every 100. for { ack, err := stream.Recv(ctx) if err != nil { if ctx.Err() != nil { return // normal shutdown } logger.Error("stream.Recv", "err", err) return } if atomic.LoadInt64(&sent)%100 == 0 { logger.Info("ack", "dedupe_key", ack.DedupeKey) } } } func randomAlert(i int) *pbv1.Alert { severities := []string{"critical", "warning", "info"} return &pbv1.Alert{ CompanyId: "acme-001", SourceId: "prom-prod", Severity: severities[rand.Intn(len(severities))], Category: "monitoring", Title: fmt.Sprintf("example alert %d", i), Body: "This is a demo alert from example-grpc-producer.", DedupeKey: fmt.Sprintf("example-%d", i), ClientTsMs: time.Now().UnixMilli(), Data: map[string]string{ "host": fmt.Sprintf("host-%d", rand.Intn(10)), "value": fmt.Sprintf("%d", rand.Intn(100)), }, } }