tales

package module
v0.0.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 29, 2025 License: MIT Imports: 14 Imported by: 0

README

kelindar/tales
Go Version PkgGoDev Go Report Card License

Multi-Actor Event Logging in S3

Tales provides a multi-actor, S3-backed log store with actor-centric queries. It keeps recent events in memory and persists them to S3 in compressed chunks.

  • Fast Writes: Non-blocking logging with sequential encoding.
  • Actor Queries: Efficient per-actor lookups using bitmaps.
  • S3 Only: Zero local storage with compressed periodic files.
  • Thread-Safe: Safe for concurrent logging from multiple goroutines.

Use When:

  • ✅ Storing chat or gameplay events for many users.
  • ✅ Needing history queries scoped to a single actor or intersection of actors.
  • ✅ Wanting predictable memory usage and S3 as the only storage.

Not For:

  • ❌ Archiving long-term logs with complex structure.
  • ❌ Applications that require random updates to existing entries.

Quick Start

import (
    "log"
    "time"
    "github.com/kelindar/tales"
)

logger, err := tales.New("my-bucket", "us-east-1",
    tales.WithPrefix("events"),
    tales.WithChunkInterval(5*time.Minute),
)
if err != nil {
    log.Fatal(err)
}
defer logger.Close()

// Log a few events (returns error)
logger.Log("Player joined", 1)
logger.Log("Player moved", 1)

from := time.Now().Add(-10 * time.Minute)
to := time.Now()

// Query for a single actor (returns an iterator)
for _, text := range logger.Query(from, to, 1) {
    println(text)
}

// Query for entries that contain ALL specified actors (intersection)
for _, text := range logger.Query(from, to, 1, 2) {
    println("Event involving both actors:", text)
}

Note: logger.Query returns an iterator (not a slice). You can use it in a for _, v := range ... loop in Go 1.21+.

Example Output

Player joined
Player moved

More Complete Example

// Log some game events
logger.Log("Player joined the game", 12345)
logger.Log("Player moved to position (100, 200)", 12345)
logger.Log("Player attacked monster", 12345, 67890) // Player and monster
logger.Log("Monster died", 67890)
logger.Log("Player gained 100 XP", 12345)

from := time.Now().Add(-1 * time.Hour)
to := time.Now().Add(1 * time.Hour)

// Query events for a specific player
for _, text := range logger.Query(from, to, 12345) {
    println(text)
}

// Query events involving both player and monster
for _, text := range logger.Query(from, to, 12345, 67890) {
    println(text)
}

Output

Player joined the game
Player moved to position (100, 200)
Player attacked monster
Player gained 100 XP
Player attacked monster

Installation

go get github.com/kelindar/tales

License

Tales is released under the MIT License.

Documentation

Overview

Example

Example demonstrates basic usage of the tales library

// Use a mock S3 server for the example
mockServer := s3mock.New("example-bucket", "us-east-1")
defer mockServer.Close()

// Create logger
logger, err := New(
	"example-bucket",
	"us-east-1",
	WithPrefix("events"),
	WithInterval(5*time.Minute),
	WithBuffer(1000),
	WithClient(func(cfg s3.Config) (s3.Client, error) {
		return s3.NewMockClient(mockServer, cfg)
	}),
)
if err != nil {
	log.Fatal(err)
}
defer logger.Close()

// Log some game events
logger.Log("Player joined the game", 12345)
logger.Log("Player moved to position (100, 200)", 12345)
logger.Log("Player attacked monster", 12345, 67890) // Player and monster
logger.Log("Monster died", 67890)
logger.Log("Player gained 100 XP", 12345)

// Query events for a specific player
from := time.Now().Add(-1 * time.Hour)
to := time.Now().Add(1 * time.Hour)

fmt.Println("Events for player 12345:")
var count int
for _, text := range logger.Query(from, to, 12345) {
	fmt.Printf("- %s\n", text)
	count++
}
fmt.Printf("Total events: %d\n", count)

// Query events involving both player and monster
fmt.Println("\nEvents involving both player 12345 and monster 67890:")
count = 0
for _, text := range logger.Query(from, to, 12345, 67890) {
	fmt.Printf("- %s\n", text)
	count++
}
fmt.Printf("Total intersection events: %d\n", count)
Output:
Events for player 12345:
- Player joined the game
- Player moved to position (100, 200)
- Player attacked monster
- Player gained 100 XP
Total events: 4

Events involving both player 12345 and monster 67890:
- Player attacked monster
Total intersection events: 1

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Logger added in v0.0.2

type Logger interface {
	Log(text string, actors ...uint32) error
}

Logger provides the capability to log events.

type Manager added in v0.0.2

type Manager interface {
	Logger
	Querier
	Close() error
}

Manager combines logging and querying capabilities and allows closing the service.

type Option

type Option func(*config)

Option configures the Service. All options are optional and may be provided to New when constructing a logger.

func WithBackblaze

func WithBackblaze() Option

WithBackblaze sets the S3 service to Backblaze B2.

func WithBuffer

func WithBuffer(size int) Option

WithBuffer sets the maximum number of entries kept in memory.

func WithCache

func WithCache(size int) Option

WithCache sets the size of the metadata LRU cache.

func WithClient

func WithClient(fn func(s3.Config) (s3.Client, error)) Option

WithClient allows overriding the S3 client creation function.

func WithInterval

func WithInterval(d time.Duration) Option

WithInterval sets the interval at which in-memory chunks are flushed.

func WithKey

func WithKey(key *aws.SigningKey) Option

WithKey sets the signing key to use for the S3 client.

func WithPrefix

func WithPrefix(prefix string) Option

WithPrefix sets the S3 key prefix to use when storing objects.

type Querier added in v0.0.2

type Querier interface {
	Query(from, to time.Time, actors ...uint32) iter.Seq2[time.Time, string]
}

Querier provides the capability to query logged events.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service provides high-performance, memory-efficient logging and querying.

func New

func New(bucket, region string, opts ...Option) (*Service, error)

New creates a new logger using the provided S3 bucket and region. Optional behaviour can be configured via Option functions.

func (*Service) Close

func (l *Service) Close() error

Close gracefully shuts down the logger, flushing any remaining data.

func (*Service) Log

func (l *Service) Log(text string, actors ...uint32) error

Log adds a log entry with the given text and actors.

func (*Service) Query

func (l *Service) Query(from, to time.Time, actors ...uint32) iter.Seq2[time.Time, string]

Query returns an iterator over log entries for the specified actors and time range. Only entries that contain ALL specified actors will be returned.

Directories

Path Synopsis
internal
s3
seq

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL