v0.3.0 tcp/http client/server add for logwisp chain connection support, config refactor

This commit is contained in:
2025-07-12 01:32:07 -04:00
parent 66f9a92592
commit 58d33d7872
9 changed files with 1691 additions and 54 deletions

View File

@ -4,14 +4,35 @@ package config
import (
"fmt"
"regexp"
"logwisp/src/internal/filter"
)
func validateFilter(pipelineName string, filterIndex int, cfg *filter.Config) error {
// FilterType represents the filter type
type FilterType string
const (
FilterTypeInclude FilterType = "include" // Whitelist - only matching logs pass
FilterTypeExclude FilterType = "exclude" // Blacklist - matching logs are dropped
)
// FilterLogic represents how multiple patterns are combined
type FilterLogic string
const (
FilterLogicOr FilterLogic = "or" // Match any pattern
FilterLogicAnd FilterLogic = "and" // Match all patterns
)
// FilterConfig represents filter configuration
type FilterConfig struct {
Type FilterType `toml:"type"`
Logic FilterLogic `toml:"logic"`
Patterns []string `toml:"patterns"`
}
func validateFilter(pipelineName string, filterIndex int, cfg *FilterConfig) error {
// Validate filter type
switch cfg.Type {
case filter.TypeInclude, filter.TypeExclude, "":
case FilterTypeInclude, FilterTypeExclude, "":
// Valid types
default:
return fmt.Errorf("pipeline '%s' filter[%d]: invalid type '%s' (must be 'include' or 'exclude')",
@ -20,7 +41,7 @@ func validateFilter(pipelineName string, filterIndex int, cfg *filter.Config) er
// Validate filter logic
switch cfg.Logic {
case filter.LogicOr, filter.LogicAnd, "":
case FilterLogicOr, FilterLogicAnd, "":
// Valid logic
default:
return fmt.Errorf("pipeline '%s' filter[%d]: invalid logic '%s' (must be 'or' or 'and')",

View File

@ -3,7 +3,8 @@ package config
import (
"fmt"
"logwisp/src/internal/filter"
"net"
"net/url"
"path/filepath"
"strings"
)
@ -17,7 +18,7 @@ type PipelineConfig struct {
Sources []SourceConfig `toml:"sources"`
// Filter configuration
Filters []filter.Config `toml:"filters"`
Filters []FilterConfig `toml:"filters"`
// Output sinks for this pipeline
Sinks []SinkConfig `toml:"sinks"`
@ -93,29 +94,38 @@ func validateSource(pipelineName string, sourceIndex int, cfg *SourceConfig) err
}
}
case "file":
// Validate file source options
path, ok := cfg.Options["path"].(string)
if !ok || path == "" {
return fmt.Errorf("pipeline '%s' source[%d]: file source requires 'path' option",
pipelineName, sourceIndex)
}
// Check for directory traversal
if strings.Contains(path, "..") {
return fmt.Errorf("pipeline '%s' source[%d]: path contains directory traversal",
pipelineName, sourceIndex)
}
case "stdin":
// No specific validation needed for stdin
case "http":
// Validate HTTP source options
port, ok := toInt(cfg.Options["port"])
if !ok || port < 1 || port > 65535 {
return fmt.Errorf("pipeline '%s' source[%d]: invalid or missing HTTP port",
pipelineName, sourceIndex)
}
// Validate path if provided
if ingestPath, ok := cfg.Options["ingest_path"].(string); ok {
if !strings.HasPrefix(ingestPath, "/") {
return fmt.Errorf("pipeline '%s' source[%d]: ingest path must start with /: %s",
pipelineName, sourceIndex, ingestPath)
}
}
case "tcp":
// Validate TCP source options
port, ok := toInt(cfg.Options["port"])
if !ok || port < 1 || port > 65535 {
return fmt.Errorf("pipeline '%s' source[%d]: invalid or missing TCP port",
pipelineName, sourceIndex)
}
default:
return fmt.Errorf("pipeline '%s' source[%d]: unknown source type '%s'",
pipelineName, sourceIndex, cfg.Type)
}
// Note: RateLimit field is ignored for now as it's a placeholder
return nil
}
@ -228,6 +238,72 @@ func validateSink(pipelineName string, sinkIndex int, cfg *SinkConfig, allPorts
}
}
case "http_client":
// Validate URL
urlStr, ok := cfg.Options["url"].(string)
if !ok || urlStr == "" {
return fmt.Errorf("pipeline '%s' sink[%d]: http_client sink requires 'url' option",
pipelineName, sinkIndex)
}
// Validate URL format
parsedURL, err := url.Parse(urlStr)
if err != nil {
return fmt.Errorf("pipeline '%s' sink[%d]: invalid URL: %w",
pipelineName, sinkIndex, err)
}
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
return fmt.Errorf("pipeline '%s' sink[%d]: URL must use http or https scheme",
pipelineName, sinkIndex)
}
// Validate batch size
if batchSize, ok := toInt(cfg.Options["batch_size"]); ok {
if batchSize < 1 {
return fmt.Errorf("pipeline '%s' sink[%d]: batch_size must be positive: %d",
pipelineName, sinkIndex, batchSize)
}
}
// Validate timeout
if timeout, ok := toInt(cfg.Options["timeout_seconds"]); ok {
if timeout < 1 {
return fmt.Errorf("pipeline '%s' sink[%d]: timeout_seconds must be positive: %d",
pipelineName, sinkIndex, timeout)
}
}
case "tcp_client":
// FIXED: Added validation for TCP client sink
// Validate address
address, ok := cfg.Options["address"].(string)
if !ok || address == "" {
return fmt.Errorf("pipeline '%s' sink[%d]: tcp_client sink requires 'address' option",
pipelineName, sinkIndex)
}
// Validate address format
_, _, err := net.SplitHostPort(address)
if err != nil {
return fmt.Errorf("pipeline '%s' sink[%d]: invalid address format (expected host:port): %w",
pipelineName, sinkIndex, err)
}
// Validate timeouts
if dialTimeout, ok := toInt(cfg.Options["dial_timeout_seconds"]); ok {
if dialTimeout < 1 {
return fmt.Errorf("pipeline '%s' sink[%d]: dial_timeout_seconds must be positive: %d",
pipelineName, sinkIndex, dialTimeout)
}
}
if writeTimeout, ok := toInt(cfg.Options["write_timeout_seconds"]); ok {
if writeTimeout < 1 {
return fmt.Errorf("pipeline '%s' sink[%d]: write_timeout_seconds must be positive: %d",
pipelineName, sinkIndex, writeTimeout)
}
}
case "file":
// Validate file sink options
directory, ok := cfg.Options["directory"].(string)