diff --git a/.gitignore b/.gitignore index e2a31d8..11e3e03 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,5 @@ cert bin script build -test *.log *.toml diff --git a/config/logwisp.toml.defaults b/config/logwisp.toml.defaults index f0f8cf8..8fde11f 100644 --- a/config/logwisp.toml.defaults +++ b/config/logwisp.toml.defaults @@ -12,7 +12,7 @@ config_save_on_exit = false # Persist runtime changes ### Logging Configuration [logging] -output = "stdout" # file|stdout|stderr|both|all|none +output = "stdout" # file|stdout|stderr|split|all|none level = "info" # debug|info|warn|error [logging.file] diff --git a/go.mod b/go.mod index 644e437..5353093 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.25.1 require ( github.com/golang-jwt/jwt/v5 v5.3.0 github.com/lixenwraith/config v0.0.0-20250908085506-537a4d49d2c3 - github.com/lixenwraith/log v0.0.0-20250929084748-210374d95b3e + github.com/lixenwraith/log v0.0.0-20250929145347-45cc8a5099c2 github.com/panjf2000/gnet/v2 v2.9.4 github.com/valyala/fasthttp v1.66.0 golang.org/x/crypto v0.42.0 diff --git a/go.sum b/go.sum index 9225793..fce8071 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/lixenwraith/config v0.0.0-20250908085506-537a4d49d2c3 h1:+RwUb7dUz9mG github.com/lixenwraith/config v0.0.0-20250908085506-537a4d49d2c3/go.mod h1:I7ddNPT8MouXXz/ae4DQfBKMq5EisxdDLRX0C7Dv4O0= github.com/lixenwraith/log v0.0.0-20250929084748-210374d95b3e h1:/XWCqFdSOiUf0/a5a63GHsvEdpglsYfn3qieNxTeyDc= github.com/lixenwraith/log v0.0.0-20250929084748-210374d95b3e/go.mod h1:E7REMCVTr6DerzDtd2tpEEaZ9R9nduyAIKQFOqHqKr0= +github.com/lixenwraith/log v0.0.0-20250929145347-45cc8a5099c2 h1:9Qf+BR83sKjok2E1Nct+3Sfzoj2dLGwC/zyQDVNmmqs= +github.com/lixenwraith/log v0.0.0-20250929145347-45cc8a5099c2/go.mod h1:E7REMCVTr6DerzDtd2tpEEaZ9R9nduyAIKQFOqHqKr0= github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg= github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= github.com/panjf2000/gnet/v2 v2.9.4 h1:XvPCcaFwO4XWg4IgSfZnNV4dfDy5g++HIEx7sH0ldHc= diff --git a/src/cmd/logwisp/bootstrap.go b/src/cmd/logwisp/bootstrap.go index a45cf76..e81dcea 100644 --- a/src/cmd/logwisp/bootstrap.go +++ b/src/cmd/logwisp/bootstrap.go @@ -50,16 +50,10 @@ func initializeLogger(cfg *config.Config) error { logger = log.NewLogger() logCfg := log.DefaultConfig() - // Prevent empty directory creation if file logging is not configured - if cfg.Logging.Output != "file" && cfg.Logging.Output != "all" { - logCfg.DisableFile = true - logCfg.Directory = "" - } - if cfg.Quiet { // In quiet mode, disable ALL logging output logCfg.Level = 255 // A level that disables all output - logCfg.DisableFile = true + logCfg.EnableFile = false logCfg.EnableConsole = false return logger.ApplyConfig(logCfg) } @@ -74,22 +68,26 @@ func initializeLogger(cfg *config.Config) error { // Configure based on output mode switch cfg.Logging.Output { case "none": + logCfg.EnableFile = false logCfg.EnableConsole = false case "stdout": + logCfg.EnableFile = false logCfg.EnableConsole = true logCfg.ConsoleTarget = "stdout" case "stderr": + logCfg.EnableFile = false logCfg.EnableConsole = true logCfg.ConsoleTarget = "stderr" case "split": + logCfg.EnableFile = false logCfg.EnableConsole = true logCfg.ConsoleTarget = "split" case "file": - logCfg.DisableFile = false + logCfg.EnableFile = true logCfg.EnableConsole = false configureFileLogging(logCfg, cfg) case "all": - logCfg.DisableFile = false + logCfg.EnableFile = true logCfg.EnableConsole = true logCfg.ConsoleTarget = "split" configureFileLogging(logCfg, cfg) diff --git a/src/internal/service/service.go b/src/internal/service/service.go index ce1b909..34a8058 100644 --- a/src/internal/service/service.go +++ b/src/internal/service/service.go @@ -121,6 +121,11 @@ func (s *Service) NewPipeline(cfg config.PipelineConfig) error { pipeline.Sinks = append(pipeline.Sinks, sinkInst) } + // Configure authentication for sources that support it before starting them + for _, sourceInst := range pipeline.Sources { + sourceInst.SetAuth(cfg.Auth) + } + // Start all sources for i, src := range pipeline.Sources { if err := src.Start(); err != nil { @@ -129,9 +134,9 @@ func (s *Service) NewPipeline(cfg config.PipelineConfig) error { } } - // Configure authentication for sources that support it - for _, sourceInst := range pipeline.Sources { - sourceInst.SetAuth(cfg.Auth) + // Configure authentication for sinks that support it before starting them + for _, sinkInst := range pipeline.Sinks { + sinkInst.SetAuth(cfg.Auth) } // Start all sinks @@ -142,11 +147,6 @@ func (s *Service) NewPipeline(cfg config.PipelineConfig) error { } } - // Configure authentication for sinks that support it - for _, sinkInst := range pipeline.Sinks { - sinkInst.SetAuth(cfg.Auth) - } - // Wire sources to sinks through filters s.wirePipeline(pipeline) diff --git a/src/internal/sink/http.go b/src/internal/sink/http.go index 69ca9c8..20bcf50 100644 --- a/src/internal/sink/http.go +++ b/src/internal/sink/http.go @@ -225,6 +225,7 @@ func (h *HTTPSink) Start(ctx context.Context) error { fasthttpLogger := compat.NewFastHTTPAdapter(h.logger) h.server = &fasthttp.Server{ + Name: fmt.Sprintf("LogWisp/%s", version.Short()), Handler: h.requestHandler, DisableKeepalive: false, StreamRequestBody: true, diff --git a/src/internal/sink/http_client.go b/src/internal/sink/http_client.go index 0646247..fee738d 100644 --- a/src/internal/sink/http_client.go +++ b/src/internal/sink/http_client.go @@ -8,8 +8,6 @@ import ( "crypto/x509" "encoding/base64" "fmt" - "logwisp/src/internal/auth" - "logwisp/src/internal/config" "net/url" "os" "strings" @@ -17,8 +15,11 @@ import ( "sync/atomic" "time" + "logwisp/src/internal/auth" + "logwisp/src/internal/config" "logwisp/src/internal/core" "logwisp/src/internal/format" + "logwisp/src/internal/version" "github.com/lixenwraith/log" "github.com/valyala/fasthttp" @@ -441,6 +442,8 @@ func (h *HTTPClientSink) sendBatch(batch []core.LogEntry) { req.Header.SetContentType("application/json") req.SetBody(body) + req.Header.Set("User-Agent", fmt.Sprintf("LogWisp/%s", version.Short())) + // Add Basic Auth header if credentials configured if h.config.Username != "" && h.config.Password != "" { creds := h.config.Username + ":" + h.config.Password diff --git a/src/internal/source/http.go b/src/internal/source/http.go index 4ce8d0d..95fcd78 100644 --- a/src/internal/source/http.go +++ b/src/internal/source/http.go @@ -4,16 +4,17 @@ package source import ( "encoding/json" "fmt" - "logwisp/src/internal/auth" "net" "sync" "sync/atomic" "time" + "logwisp/src/internal/auth" "logwisp/src/internal/config" "logwisp/src/internal/core" "logwisp/src/internal/limit" "logwisp/src/internal/tls" + "logwisp/src/internal/version" "github.com/lixenwraith/log" "github.com/valyala/fasthttp" @@ -180,6 +181,7 @@ func (h *HTTPSource) Subscribe() <-chan core.LogEntry { func (h *HTTPSource) Start() error { h.server = &fasthttp.Server{ + Name: fmt.Sprintf("LogWisp/%s", version.Short()), Handler: h.requestHandler, DisableKeepalive: false, StreamRequestBody: true, diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..de0fb6a --- /dev/null +++ b/test/README.md @@ -0,0 +1,12 @@ +### Usage: + +- Copy logwisp executable to the test folder (to compile, in logwisp top directory: `make build`). + +- Run the test script for each scenario. + +### Notes: + +- The tests create configuration files and log files. Debug tests do not clean up these files. + +- Some tests may require to be run on different hosts (containers can be used). + diff --git a/test/test-logwisp-auth-debug.sh b/test/test-logwisp-auth-debug.sh new file mode 100755 index 0000000..8bddd43 --- /dev/null +++ b/test/test-logwisp-auth-debug.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env bash +# FILE: test-logwisp-auth-debug.sh + +# Creates test directories and starts network services +set -e + +# Create test directories +mkdir -p test-logs test-data + +# Generate Argon2id hash using logwisp auth +echo "=== Generating Argon2id hash ===" +./logwisp auth -u testuser -p secret123 > auth_output.txt 2>&1 +HASH=$(grep 'password_hash = ' auth_output.txt | cut -d'"' -f2) +if [ -z "$HASH" ]; then + echo "Failed to generate hash. Output:" + cat auth_output.txt + exit 1 +fi +echo "Generated hash format: ${HASH:0:15}..." # Show hash format prefix +echo "Full hash: $HASH" + +# Determine hash type +if [[ "$HASH" == "\$argon2id\$"* ]]; then + echo "Hash type: Argon2id" +elif [[ "$HASH" == "\$2a\$"* ]] || [[ "$HASH" == "\$2b\$"* ]]; then + echo "Hash type: bcrypt" +else + echo "Hash type: Unknown" +fi + +# Create test config with debug logging to stdout +cat > test-auth.toml << EOF +# General LogWisp settings +log_dir = "test-logs" +log_level = "debug" # CHANGED: Set to debug +data_dir = "test-data" + +# Logging configuration for troubleshooting +[logging] +target = "all" +level = "debug" +[logging.console] +enabled = true +target = "stdout" # CHANGED: Log to stdout for visibility +format = "txt" + +[[pipelines]] +name = "tcp-test" +[pipelines.auth] +type = "basic" +[[pipelines.auth.basic_auth.users]] +username = "testuser" +password_hash = "$HASH" + +[[pipelines.sources]] +type = "tcp" +[pipelines.sources.options] +port = 5514 +host = "127.0.0.1" + +[[pipelines.sinks]] +type = "stdout" + +# Second pipeline for HTTP +[[pipelines]] +name = "http-test" +[pipelines.auth] +type = "basic" +[[pipelines.auth.basic_auth.users]] +username = "httpuser" +password_hash = "$HASH" + +[[pipelines.sources]] +type = "http" +[pipelines.sources.options] +port = 8080 +host = "127.0.0.1" +path = "/ingest" + +[[pipelines.sinks]] +type = "stdout" # CHANGED: Simplify to stdout for debugging +EOF + +# Start LogWisp with visible debug output +echo "=== Starting LogWisp with debug logging ===" +./logwisp -c test-auth.toml 2>&1 | tee logwisp-debug.log & +LOGWISP_PID=$! + +# Wait for startup with longer timeout +echo "Waiting for LogWisp to start..." +for i in {1..20}; do + if nc -z 127.0.0.1 5514 2>/dev/null && nc -z 127.0.0.1 8080 2>/dev/null; then + echo "LogWisp started successfully" + break + fi + if [ $i -eq 20 ]; then + echo "LogWisp failed to start. Check logwisp-debug.log" + kill $LOGWISP_PID 2>/dev/null || true + exit 1 + fi + sleep 1 +done + +# Give extra time for auth initialization +sleep 2 + +echo "=== Testing HTTP Auth ===" + +# Test with verbose curl to see headers +echo "Testing no auth (expecting 401)..." +curl -v -s -o response.txt -w "STATUS:%{http_code}\n" \ + http://127.0.0.1:8080/ingest -d '{"test":"data"}' 2>&1 | tee curl-noauth.log | grep -E "STATUS:|< HTTP" + +# Test invalid auth +echo "Testing invalid auth (expecting 401)..." +curl -v -s -o response.txt -w "STATUS:%{http_code}\n" \ + -u baduser:badpass http://127.0.0.1:8080/ingest -d '{"test":"data"}' 2>&1 | tee curl-badauth.log | grep -E "STATUS:|< HTTP" + +# Test valid auth with detailed output +echo "Testing valid auth (expecting 202/200)..." +curl -v -s -o response.txt -w "STATUS:%{http_code}\n" \ + -u httpuser:secret123 http://127.0.0.1:8080/ingest \ + -H "Content-Type: application/json" \ + -d '{"message":"test log","level":"info"}' 2>&1 | tee curl-validauth.log | grep -E "STATUS:|< HTTP" + +# Show response body if not 200/202 +STATUS=$(grep "STATUS:" curl-validauth.log | cut -d: -f2) +if [ "$STATUS" != "200" ] && [ "$STATUS" != "202" ]; then + echo "Response body:" + cat response.txt +fi + +# Check logs for auth-related errors +echo "=== Checking logs for auth errors ===" +grep -i "auth" logwisp-debug.log | grep -i "error" | tail -5 || echo "No auth errors found" +grep -i "authenticator" logwisp-debug.log | tail -5 || echo "No authenticator messages" + +# Cleanup +echo "=== Cleanup ===" +kill $LOGWISP_PID 2>/dev/null || true +echo "Logs saved to logwisp-debug.log, curl-*.log" +# Optionally keep logs for analysis +# rm -f test-auth.toml auth_output.txt response.txt +# rm -rf test-logs test-data