Official Go client for Streamline - The Redis of Streaming.
- Idiomatic Go with context support
- Producer with batching and compression
- Consumer with group coordination
- Admin client for topic management
- Query client for SQL analytics
- SASL/SCRAM authentication support
- TLS support
go get github.com/streamlinelabs/streamline-go-sdk/streamlineThe SDK includes built-in OpenTelemetry tracing via TracingProducer and
TracingConsumer wrappers. These wrappers automatically create spans for
produce and consume operations and propagate trace context through message
headers.
import "github.com/streamlinelabs/streamline-go-sdk/streamline"
// Wrap an existing producer with tracing
tracingProducer := streamline.NewTracingProducer(client.Producer)
// Wrap an existing consumer with tracing
tracingConsumer := streamline.NewTracingConsumer(consumer)// Sends a message with automatic span creation and context injection
result, err := tracingProducer.Send(ctx, "orders", []byte("key"), []byte("value"))messages, errors := tracingConsumer.Start(ctx)
for msg := range messages {
// Create a processing span linked to the producer trace
processCtx, span := tracingConsumer.TraceProcess(ctx, msg)
processMessage(processCtx, msg)
span.End()
}| Attribute | Value |
|---|---|
| Span name | {topic} {operation} (e.g., "orders produce") |
messaging.system |
streamline |
messaging.destination.name |
Topic name |
messaging.operation |
produce, consume, or process |
| Span kind | PRODUCER for produce, CONSUMER for consume |
Trace context is propagated via W3C TraceContext headers in messages.
package main
import (
"context"
"log"
"github.com/streamlinelabs/streamline-go-sdk/streamline"
)
func main() {
// Create client
config := streamline.DefaultConfig()
config.Brokers = []string{"localhost:9092"}
client, err := streamline.NewClient(config)
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
// Produce a message
result, err := client.Producer.Send(ctx, "my-topic", nil, []byte("Hello, World!"))
if err != nil {
log.Fatal(err)
}
log.Printf("Produced to partition %d at offset %d", result.Partition, result.Offset)
}result, err := client.Producer.Send(ctx, "topic", []byte("key"), []byte("value"))result, err := client.Producer.SendMessage(ctx, &streamline.Message{
Topic: "topic",
Key: []byte("key"),
Value: []byte("value"),
Headers: map[string][]byte{
"trace-id": []byte("abc123"),
},
})messages := []*streamline.Message{
{Topic: "topic", Value: []byte("msg1")},
{Topic: "topic", Value: []byte("msg2")},
{Topic: "topic", Value: []byte("msg3")},
}
results, err := client.Producer.SendBatch(ctx, messages)resultCh := client.Producer.SendAsync(&streamline.Message{
Topic: "topic",
Value: []byte("async message"),
})
result := <-resultCh
if result.Err != nil {
log.Printf("Error: %v", result.Err)
} else {
log.Printf("Sent to partition %d", result.Partition)
}The Go SDK supports client-side buffered transactions for atomic batch sends:
producer := client.Producer()
producer.BeginTransaction()
producer.SendTransactional(ctx, &streamline.Message{Topic: "orders", Key: []byte("k1"), Value: []byte("v1")})
producer.SendTransactional(ctx, &streamline.Message{Topic: "orders", Key: []byte("k2"), Value: []byte("v2")})
results, err := producer.CommitTransaction(ctx)
if err != nil {
producer.AbortTransaction()
}Note: Transactions use client-side buffering — messages are collected locally and sent as a batch on commit. This provides all-or-nothing delivery semantics at the client level. If you need broker-level exactly-once semantics, combine with idempotent producers.
consumer, err := client.NewConsumer(ctx, "my-group", []string{"my-topic"})
if err != nil {
log.Fatal(err)
}
defer consumer.Close()
messages, errors := consumer.Start(ctx)
for {
select {
case msg := <-messages:
log.Printf("Received: %s", string(msg.Value))
case err := <-errors:
log.Printf("Error: %v", err)
case <-ctx.Done():
return
}
}messages, err := consumer.Poll(ctx, 100, 5*time.Second)
for _, msg := range messages {
log.Printf("Received: %s", string(msg.Value))
}err := client.Admin.CreateTopic(ctx, streamline.TopicConfig{
Name: "my-topic",
NumPartitions: 3,
ReplicationFactor: 1,
Config: map[string]string{
"retention.ms": "86400000",
},
})topics, err := client.Admin.ListTopics(ctx)
for _, t := range topics {
log.Printf("Topic: %s, Partitions: %d", t.Name, t.Partitions)
}info, partitions, err := client.Admin.DescribeTopic(ctx, "my-topic")
log.Printf("Topic: %s", info.Name)
for _, p := range partitions {
log.Printf(" Partition %d: leader=%d, replicas=%v", p.ID, p.Leader, p.Replicas)
}// List consumer groups
groups, err := client.Admin.ListConsumerGroups(ctx)
// Describe consumer group
info, err := client.Admin.DescribeConsumerGroup(ctx, "my-group")
// Reset offsets to earliest
err := client.Admin.ResetConsumerGroupOffsets(ctx, "my-group", "my-topic", -2)
// Reset offsets to latest
err := client.Admin.ResetConsumerGroupOffsets(ctx, "my-group", "my-topic", -1)The HTTPAdmin client communicates with the Streamline HTTP REST API for operations not available via the Kafka wire protocol.
admin := streamline.NewHTTPAdmin("http://localhost:9094")
// Cluster overview
cluster, err := admin.ClusterInfo(ctx)
fmt.Printf("Cluster: %s, Brokers: %d\n", cluster.ClusterID, len(cluster.Brokers))
// Consumer group lag monitoring
lag, err := admin.ConsumerGroupLag(ctx, "my-group")
fmt.Printf("Total lag: %d\n", lag.TotalLag)
for _, p := range lag.Partitions {
fmt.Printf(" %s:%d lag=%d\n", p.Topic, p.Partition, p.Lag)
}
// Message inspection
messages, err := admin.InspectMessages(ctx, "events", 0, nil, 10)
for _, m := range messages {
fmt.Printf("offset=%d key=%v value=%s\n", m.Offset, m.Key, m.Value)
}
// Latest messages
latest, err := admin.LatestMessages(ctx, "events", 5)
// Server metrics
metrics, err := admin.MetricsHistory(ctx)
for _, m := range metrics {
fmt.Printf("%s=%f %v\n", m.Name, m.Value, m.Labels)
}queryClient := streamline.NewQueryClient("http://localhost:9094")
result, err := queryClient.Query(ctx, "SELECT * FROM topic('events') LIMIT 10")
if err != nil {
log.Fatal(err)
}
for _, row := range result.Rows {
fmt.Println(row)
}
fmt.Printf("Scanned %d rows in %dms\n", result.Metadata.RowsScanned, result.Metadata.ExecutionTimeMs)opts := streamline.QueryOptions{
TimeoutMs: 5000,
MaxRows: 100,
}
result, err := queryClient.QueryWithOptions(ctx, "SELECT * FROM topic('events') ORDER BY offset DESC", opts)plan, err := queryClient.Explain(ctx, "SELECT * FROM topic('events') WHERE key = 'user-123'")
if err != nil {
log.Fatal(err)
}
fmt.Println(plan)config := streamline.DefaultConfig()
config.Producer = streamline.ProducerConfig{
RequiredAcks: -1, // All replicas
Compression: 1, // gzip
BatchSize: 16384, // 16KB
BatchTimeout: 10 * time.Millisecond,
Idempotent: true, // Enable EOS
Retries: 3,
}config.Consumer = streamline.ConsumerConfig{
GroupID: "my-group",
AutoOffsetReset: "earliest",
SessionTimeout: 30 * time.Second,
HeartbeatInterval: 3 * time.Second,
MaxPollRecords: 500,
IsolationLevel: 1, // Read committed
}config.SASL = &streamline.SASLConfig{
Mechanism: "SCRAM-SHA-256",
Username: "user",
Password: "password",
}config.TLS = &streamline.TLSConfig{
Enable: true,
CertFile: "/path/to/client.crt",
KeyFile: "/path/to/client.key",
CAFile: "/path/to/ca.crt",
}All errors returned by the SDK implement the standard error interface. Structured errors are returned as *StreamlineError with error codes, hints, and retryability information.
result, err := client.Producer.Send(ctx, "my-topic", nil, []byte("data"))
if err != nil {
var se *streamline.StreamlineError
if errors.As(err, &se) {
switch se.Code {
case streamline.ErrTopicNotFound:
log.Printf("Topic missing: %s (hint: %s)", se.Message, se.Hint)
case streamline.ErrAuthentication:
log.Fatalf("Auth failed: %s", se.Message)
case streamline.ErrTimeout:
log.Printf("Timeout — retryable: %v", se.Retryable)
default:
log.Printf("Error [%s]: %s", se.Code, se.Message)
}
} else {
log.Printf("Unexpected error: %v", err)
}
}| Code | Constant | Retryable | Description |
|---|---|---|---|
CONNECTION_ERROR |
ErrConnection |
✅ | Server unreachable or connection dropped |
AUTHENTICATION_ERROR |
ErrAuthentication |
❌ | SASL/TLS credentials rejected |
AUTHORIZATION_ERROR |
ErrAuthorization |
❌ | ACL denied the operation |
TOPIC_NOT_FOUND |
ErrTopicNotFound |
❌ | Topic does not exist |
TIMEOUT |
ErrTimeout |
✅ | Operation exceeded deadline |
PRODUCER_ERROR |
ErrProducer |
✅ | Send failed (network, batch, etc.) |
CONSUMER_ERROR |
ErrConsumer |
✅ | Consume/poll failed |
SERIALIZATION_ERROR |
ErrSerialization |
❌ | Message encode/decode failed |
CONFIGURATION_ERROR |
ErrConfiguration |
❌ | Invalid client configuration |
INTERNAL_ERROR |
ErrInternal |
❌ | Unexpected internal error |
// Check if any error is a StreamlineError
if streamline.IsStreamlineError(err) {
code := streamline.GetErrorCode(err)
log.Printf("Streamline error code: %s", code)
}
// Check if an error is safe to retry
if streamline.IsRetryable(err) {
// Back off and retry the operation
}
// Unwrap to inspect the underlying cause
var se *streamline.StreamlineError
if errors.As(err, &se) && se.Err != nil {
log.Printf("Caused by: %v", se.Err)
}The SDK includes a circuit breaker that protects your application from cascading failures. The Producer uses it automatically; you can also use it directly:
import "github.com/streamlinelabs/streamline-go-sdk/streamline"
cb := streamline.NewCircuitBreaker(streamline.CircuitBreakerConfig{
FailureThreshold: 5, // Open after 5 consecutive failures
SuccessThreshold: 2, // Close after 2 half-open successes
OpenTimeout: 30 * time.Second, // Probe interval
OnStateChange: func(from, to streamline.CircuitState) {
log.Printf("Circuit: %s → %s", from, to)
},
})
if cb.Allow() {
err := doSomething()
if err != nil {
cb.RecordFailure()
} else {
cb.RecordSuccess()
}
}When the circuit is open, Allow() returns false and operations are rejected immediately. See the Circuit Breaker guide for details.
| Method | Description |
|---|---|
NewClient(config) |
Create a new client |
client.Close() |
Close the client |
client.NewConsumer(ctx, groupID, topics) |
Create a consumer |
| Method | Description |
|---|---|
Send(ctx, topic, key, value) |
Send a message synchronously |
SendMessage(ctx, msg) |
Send a message with full options |
SendAsync(msg) |
Send a message asynchronously |
SendBatch(ctx, messages) |
Send multiple messages |
Close() |
Close the producer |
| Method | Description |
|---|---|
Start(ctx) |
Start consuming, returns message and error channels |
Poll(ctx, maxRecords, timeout) |
Poll for messages |
Commit() |
Commit offsets |
Close() |
Close the consumer |
| Method | Description |
|---|---|
CreateTopic(ctx, config) |
Create a topic |
DeleteTopic(ctx, name) |
Delete a topic |
ListTopics(ctx) |
List all topics |
DescribeTopic(ctx, name) |
Get topic details |
AddPartitions(ctx, name, count) |
Add partitions |
ListConsumerGroups(ctx) |
List consumer groups |
DescribeConsumerGroup(ctx, groupID) |
Get group details |
DeleteConsumerGroup(ctx, groupID) |
Delete a consumer group |
ResetConsumerGroupOffsets(ctx, groupID, topic, offset) |
Reset offsets |
| Method | Description |
|---|---|
NewQueryClient(baseURL) |
Create a query client for the HTTP API |
Query(ctx, sql) |
Execute a SQL query with default options |
QueryWithOptions(ctx, sql, opts) |
Execute a SQL query with custom options |
Explain(ctx, sql) |
Get the query execution plan |
- Go 1.22 or later
- Streamline server 0.2.0 or later
The examples/ directory contains runnable examples:
| Example | Description |
|---|---|
| Basic Usage | Produce, consume, and admin operations |
| Query Usage | SQL analytics with the embedded query engine |
| Schema Registry | Schema registration and validation |
| Circuit Breaker | Resilient production with circuit breaker |
| Security | TLS and SASL authentication |
Run any example:
go run examples/main.go
go run examples/circuit_breaker/main.go
⚠️ Experimental — These features require Streamline server 0.3.0+ with moonshot feature flags enabled.
Query topics by meaning instead of offset. Requires a topic created with semantic.embed=true.
results, err := consumer.Search(ctx, "logs.app", "payment failure", 10)
if err != nil {
log.Fatal(err)
}
for _, hit := range results {
log.Printf("[p%d] offset=%d score=%.2f", hit.Partition, hit.Offset, hit.Score)
}Verify cryptographic provenance attestations attached to records by data contracts.
verifier, err := streamline.NewVerifier(publicKeyBytes)
if err != nil {
log.Fatal(err)
}
result, err := verifier.Verify(record)
log.Printf("Verified: %v, Producer: %s", result.Verified, result.ProducerID)Use Streamline as persistent memory for AI agents via the MCP protocol.
memory := streamline.NewMemoryClient("http://localhost:9094/mcp/v1")
err := memory.Remember(ctx, "user prefers dark mode", streamline.MemoryTags("preferences"))
results, err := memory.Recall(ctx, "user preferences", 5)Create topic branches for replay, A/B testing, or counterfactual analysis.
branch, err := client.Admin.CreateBranch(ctx, "events", "experiment-v2")
if err != nil {
log.Fatal(err)
}
consumer, err := client.NewConsumer(ctx, "branch-group", []string{branch.Topic})
messages, errors := consumer.Start(ctx)Contributions are welcome! Please see the organization contributing guide for guidelines.
Apache-2.0
To report a security vulnerability, please email security@streamline.dev. Do not open a public issue.
See the Security Policy for details.