# go-ja4common `ja4common` is the shared Go library for the ja4-platform, providing unified logging, YAML configuration loading with environment variable overrides, graceful shutdown handling, and IP address filtering. It is used by both [sentinel](../services/sentinel.md) and [correlator](../services/correlator.md). **Module path**: `github.com/antitbone/ja4/ja4common` **Go version**: 1.21+ **Dependencies**: `gopkg.in/yaml.v3` ## Packages ### logger Unified structured logging with two styles: - **Prefix+Fields style** (correlator pattern) — `Logger` - **Component style** (sentinel pattern) — `ComponentLogger` #### Types ```go type LogLevel int const ( DEBUG LogLevel = iota INFO WARN ERROR ) ``` #### Logger API | Method | Signature | Description | |--------|-----------|-------------| | `New` | `New(prefix string) *Logger` | Create logger with INFO level | | `NewWithLevel` | `NewWithLevel(prefix, level string) *Logger` | Create logger with specified level | | `SetLevel` | `(l *Logger) SetLevel(level string)` | Change minimum log level at runtime | | `ShouldLog` | `(l *Logger) ShouldLog(level LogLevel) bool` | Check if level would be logged | | `WithFields` | `(l *Logger) WithFields(fields map[string]any) *Logger` | Return new logger with additional fields | | `Info` | `(l *Logger) Info(msg string)` | Log info message | | `Infof` | `(l *Logger) Infof(msg string, args ...any)` | Log formatted info | | `Warn` | `(l *Logger) Warn(msg string)` | Log warning | | `Warnf` | `(l *Logger) Warnf(msg string, args ...any)` | Log formatted warning | | `Error` | `(l *Logger) Error(msg string, err error)` | Log error with optional error value | | `Debug` | `(l *Logger) Debug(msg string)` | Log debug message | | `Debugf` | `(l *Logger) Debugf(msg string, args ...any)` | Log formatted debug | | `ParseLogLevel` | `ParseLogLevel(level string) LogLevel` | Parse string to LogLevel | #### ComponentLogger API Wraps `Logger` to satisfy sentinel's component-based logging interface: | Method | Signature | Description | |--------|-----------|-------------| | `NewComponentLogger` | `NewComponentLogger(level string) *ComponentLogger` | Create component logger | | `Log` | `(c *ComponentLogger) Log(component, level, message string, details map[string]string)` | Log with component context | | `Debug` | `(c *ComponentLogger) Debug(component, message string, details map[string]string)` | Debug with component | | `Info` | `(c *ComponentLogger) Info(component, message string, details map[string]string)` | Info with component | | `Warn` | `(c *ComponentLogger) Warn(component, message string, details map[string]string)` | Warn with component | | `Error` | `(c *ComponentLogger) Error(component, message string, details map[string]string)` | Error with component | #### Usage Example ```go import "github.com/antitbone/ja4/ja4common/logger" // Prefix+Fields style log := logger.NewWithLevel("myservice", "DEBUG") log.Info("starting up") log.WithFields(map[string]any{"port": 8080}).Info("listening") // Component style (sentinel compatibility) clog := logger.NewComponentLogger("info") clog.Info("capture", "packets received", map[string]string{"count": "1000"}) ``` --- ### config Generic YAML configuration loading with environment variable overrides using struct tags. #### API | Function | Signature | Description | |----------|-----------|-------------| | `LoadYAML` | `LoadYAML[T any](path string, optional bool) (T, error)` | Load and unmarshal YAML file | | `OverrideFromEnv` | `OverrideFromEnv[T any](cfg *T, envPrefix string) error` | Apply env var overrides via `env` struct tags | #### Supported Types for Environment Override - `string` - `int`, `int8`, `int16`, `int32`, `int64` - `uint`, `uint8`, `uint16`, `uint32`, `uint64` - `bool` - `[]string` (comma-separated) #### Usage Example ```go import "github.com/antitbone/ja4/ja4common/config" type MyConfig struct { Host string `yaml:"host" env:"HOST"` Port int `yaml:"port" env:"PORT"` Debug bool `yaml:"debug" env:"DEBUG"` Tags []string `yaml:"tags" env:"TAGS"` } // Load YAML (optional=true means missing file returns zero value) cfg, err := config.LoadYAML[MyConfig]("config.yml", true) // Override from environment (prefix="" means use tag directly) err = config.OverrideFromEnv(&cfg, "MYAPP") // Reads: MYAPP_HOST, MYAPP_PORT, MYAPP_DEBUG, MYAPP_TAGS ``` --- ### shutdown Graceful shutdown handler that blocks until `SIGTERM`/`SIGINT`, then runs cleanup hooks. #### API ```go type Hook struct { Name string Fn func() error } func Handle(ctx context.Context, cancel context.CancelFunc, hooks []Hook, logger simpleLogger) ``` The `Handle` function: 1. Blocks until `SIGTERM`, `SIGINT`, or context cancellation 2. Calls `cancel()` to propagate shutdown 3. Runs all hooks in order, logging errors but not aborting #### Usage Example ```go import "github.com/antitbone/ja4/ja4common/shutdown" ctx, cancel := context.WithCancel(context.Background()) hooks := []shutdown.Hook{ {Name: "close-db", Fn: func() error { return db.Close() }}, {Name: "flush-logs", Fn: func() error { return logger.Flush() }}, } // This blocks until signal received shutdown.Handle(ctx, cancel, hooks, myLogger) ``` --- ### ipfilter IP address and CIDR range matching for source IP exclusion. #### API | Method | Signature | Description | |--------|-----------|-------------| | `New` | `New(excludeList []string) (*Filter, error)` | Create filter from IP/CIDR list | | `ShouldExclude` | `(f *Filter) ShouldExclude(ipStr string) bool` | Check if IP should be excluded | | `Count` | `(f *Filter) Count() (ips int, networks int)` | Return number of loaded entries | Accepts: single IPs (`192.168.1.1`), CIDR ranges (`10.0.0.0/8`), IPv6 addresses and ranges. #### Usage Example ```go import "github.com/antitbone/ja4/ja4common/ipfilter" filter, err := ipfilter.New([]string{ "10.0.0.0/8", "192.168.1.1", "2001:db8::/32", }) if filter.ShouldExclude("10.0.0.5") { // Skip this IP } ips, nets := filter.Count() // 1 IP, 2 networks ``` ## Using from a New Service ### 1. Add to go.mod ```bash cd services/my-service go mod init github.com/antitbone/ja4/my-service ``` Add the dependency: ``` require github.com/antitbone/ja4/ja4common v0.0.0 ``` ### 2. Add to go.work In the repository root `go.work`: ``` use ( ./services/sentinel ./services/correlator ./services/my-service // ← add ./shared/go/ja4common ) ``` ### 3. Import and Use ```go package main import ( "context" "github.com/antitbone/ja4/ja4common/config" "github.com/antitbone/ja4/ja4common/logger" "github.com/antitbone/ja4/ja4common/shutdown" ) func main() { log := logger.NewWithLevel("myservice", "INFO") cfg, _ := config.LoadYAML[MyConfig]("config.yml", true) config.OverrideFromEnv(&cfg, "MYSERVICE") ctx, cancel := context.WithCancel(context.Background()) shutdown.Handle(ctx, cancel, nil, log) } ``` ### 4. Sync Workspace ```bash go work sync ```