Initial commit: mod_reqin_log Apache module
Features: - JSON logging of HTTP requests to Unix domain socket - Configurable HTTP headers logging (flat JSON structure) - Header value truncation and count limits - Automatic reconnect on socket disconnection - Error reporting with throttling Configuration directives: - JsonSockLogEnabled: Enable/disable logging - JsonSockLogSocket: Unix socket path - JsonSockLogHeaders: List of headers to log - JsonSockLogMaxHeaders: Maximum headers to log - JsonSockLogMaxHeaderValueLen: Max header value length - JsonSockLogReconnectInterval: Reconnect delay - JsonSockLogErrorReportInterval: Error log throttle Includes: - Module source code (src/) - Unit and integration tests (tests/, scripts/) - Documentation (README.md, architecture.yml) - Build configuration (CMakeLists.txt, Makefile) - Packaging (deb/rpm) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
27
scripts/build.sh
Executable file
27
scripts/build.sh
Executable file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# build.sh - Build mod_reqin_log in Docker
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
IMAGE_NAME="mod_reqin_log-build"
|
||||
|
||||
echo "Building Docker image..."
|
||||
docker build -t "$IMAGE_NAME" "$SCRIPT_DIR/.."
|
||||
|
||||
echo ""
|
||||
echo "Build complete. Extracting module..."
|
||||
|
||||
# Create dist directory
|
||||
mkdir -p "$SCRIPT_DIR/../dist"
|
||||
|
||||
# Extract the built module from container
|
||||
docker run --rm -v "$SCRIPT_DIR/../dist:/output" "$IMAGE_NAME" cp /build/modules/mod_reqin_log.so /output/
|
||||
|
||||
echo ""
|
||||
echo "Module built successfully: $SCRIPT_DIR/../dist/mod_reqin_log.so"
|
||||
echo ""
|
||||
echo "To test the module:"
|
||||
echo " docker run --rm -v \$PWD/dist:/modules mod_reqin_log-build httpd -t -C 'LoadModule reqin_log_module /modules/mod_reqin_log.so'"
|
||||
267
scripts/run_integration_tests.sh
Executable file
267
scripts/run_integration_tests.sh
Executable file
@ -0,0 +1,267 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# run_integration_tests.sh - Integration test script for mod_reqin_log
|
||||
#
|
||||
# This script runs integration tests for the mod_reqin_log module.
|
||||
# It requires:
|
||||
# - Apache HTTPD with the module loaded
|
||||
# - A running socket consumer
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SOCKET_PATH="/tmp/mod_reqin_log.sock"
|
||||
LOG_FILE="/tmp/mod_reqin_log_test.log"
|
||||
APACHE_URL="${APACHE_URL:-http://localhost}"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Counters
|
||||
TESTS_RUN=0
|
||||
TESTS_PASSED=0
|
||||
TESTS_FAILED=0
|
||||
|
||||
log_info() {
|
||||
echo -e "${YELLOW}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_pass() {
|
||||
echo -e "${GREEN}[PASS]${NC} $1"
|
||||
((TESTS_PASSED++))
|
||||
}
|
||||
|
||||
log_fail() {
|
||||
echo -e "${RED}[FAIL]${NC} $1"
|
||||
((TESTS_FAILED++))
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
log_info "Cleaning up..."
|
||||
rm -f "$LOG_FILE"
|
||||
rm -f "$SOCKET_PATH"
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
# Check prerequisites
|
||||
check_prerequisites() {
|
||||
log_info "Checking prerequisites..."
|
||||
|
||||
if ! command -v curl &> /dev/null; then
|
||||
echo "Error: curl is required but not installed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
echo "Error: python3 is required but not installed."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Start socket consumer
|
||||
start_consumer() {
|
||||
log_info "Starting socket consumer..."
|
||||
python3 "$SCRIPT_DIR/socket_consumer.py" "$SOCKET_PATH" -o "$LOG_FILE" &
|
||||
CONSUMER_PID=$!
|
||||
sleep 2
|
||||
|
||||
if ! kill -0 $CONSUMER_PID 2>/dev/null; then
|
||||
echo "Error: Failed to start socket consumer"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Stop socket consumer
|
||||
stop_consumer() {
|
||||
log_info "Stopping socket consumer..."
|
||||
if [ -n "$CONSUMER_PID" ]; then
|
||||
kill $CONSUMER_PID 2>/dev/null || true
|
||||
wait $CONSUMER_PID 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
# Test: Basic request logging
|
||||
test_basic_logging() {
|
||||
((TESTS_RUN++))
|
||||
log_info "Test: Basic request logging"
|
||||
|
||||
curl -s "$APACHE_URL/" > /dev/null
|
||||
sleep 1
|
||||
|
||||
if grep -q '"method":"GET"' "$LOG_FILE" 2>/dev/null; then
|
||||
log_pass "Basic logging test"
|
||||
else
|
||||
log_fail "Basic logging test - No GET method found in logs"
|
||||
fi
|
||||
}
|
||||
|
||||
# Test: Custom header logging
|
||||
test_custom_headers() {
|
||||
((TESTS_RUN++))
|
||||
log_info "Test: Custom header logging"
|
||||
|
||||
curl -s -H "X-Request-Id: test-12345" "$APACHE_URL/" > /dev/null
|
||||
sleep 1
|
||||
|
||||
if grep -q '"header_X-Request-Id":"test-12345"' "$LOG_FILE" 2>/dev/null; then
|
||||
log_pass "Custom header logging test"
|
||||
else
|
||||
log_fail "Custom header logging test - X-Request-Id not found in logs"
|
||||
fi
|
||||
}
|
||||
|
||||
# Test: Multiple headers
|
||||
test_multiple_headers() {
|
||||
((TESTS_RUN++))
|
||||
log_info "Test: Multiple headers"
|
||||
|
||||
curl -s \
|
||||
-H "X-Request-Id: req-abc" \
|
||||
-H "X-Trace-Id: trace-xyz" \
|
||||
"$APACHE_URL/" > /dev/null
|
||||
sleep 1
|
||||
|
||||
local found_request_id=$(grep -c '"header_X-Request-Id":"req-abc"' "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
local found_trace_id=$(grep -c '"header_X-Trace-Id":"trace-xyz"' "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
|
||||
if [ "$found_request_id" -gt 0 ] && [ "$found_trace_id" -gt 0 ]; then
|
||||
log_pass "Multiple headers test"
|
||||
else
|
||||
log_fail "Multiple headers test - Not all headers found"
|
||||
fi
|
||||
}
|
||||
|
||||
# Test: JSON format validation
|
||||
test_json_format() {
|
||||
((TESTS_RUN++))
|
||||
log_info "Test: JSON format validation"
|
||||
|
||||
curl -s "$APACHE_URL/" > /dev/null
|
||||
sleep 1
|
||||
|
||||
# Get last line and validate JSON
|
||||
local last_line=$(tail -1 "$LOG_FILE" 2>/dev/null | sed 's/^\[.*\] //')
|
||||
|
||||
if echo "$last_line" | python3 -m json.tool > /dev/null 2>&1; then
|
||||
log_pass "JSON format validation test"
|
||||
else
|
||||
log_fail "JSON format validation test - Invalid JSON format"
|
||||
fi
|
||||
}
|
||||
|
||||
# Test: Required fields presence
|
||||
test_required_fields() {
|
||||
((TESTS_RUN++))
|
||||
log_info "Test: Required fields presence"
|
||||
|
||||
curl -s "$APACHE_URL/" > /dev/null
|
||||
sleep 1
|
||||
|
||||
local last_line=$(tail -1 "$LOG_FILE" 2>/dev/null | sed 's/^\[.*\] //')
|
||||
|
||||
local required_fields=("time" "timestamp" "src_ip" "dst_ip" "method" "path" "host")
|
||||
local all_present=true
|
||||
|
||||
for field in "${required_fields[@]}"; do
|
||||
if ! echo "$last_line" | grep -q "\"$field\":"; then
|
||||
all_present=false
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if $all_present; then
|
||||
log_pass "Required fields presence test"
|
||||
else
|
||||
log_fail "Required fields presence test - Missing required fields"
|
||||
fi
|
||||
}
|
||||
|
||||
# Test: HTTP method variations
|
||||
test_method_variations() {
|
||||
((TESTS_RUN++))
|
||||
log_info "Test: HTTP method variations"
|
||||
|
||||
curl -s -X POST "$APACHE_URL/" > /dev/null
|
||||
curl -s -X PUT "$APACHE_URL/" > /dev/null
|
||||
curl -s -X DELETE "$APACHE_URL/" > /dev/null
|
||||
sleep 1
|
||||
|
||||
local found_post=$(grep -c '"method":"POST"' "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
local found_put=$(grep -c '"method":"PUT"' "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
local found_delete=$(grep -c '"method":"DELETE"' "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
|
||||
if [ "$found_post" -gt 0 ] && [ "$found_put" -gt 0 ] && [ "$found_delete" -gt 0 ]; then
|
||||
log_pass "HTTP method variations test"
|
||||
else
|
||||
log_fail "HTTP method variations test - Not all methods found"
|
||||
fi
|
||||
}
|
||||
|
||||
# Test: Path logging
|
||||
test_path_logging() {
|
||||
((TESTS_RUN++))
|
||||
log_info "Test: Path logging"
|
||||
|
||||
curl -s "$APACHE_URL/api/users" > /dev/null
|
||||
curl -s "$APACHE_URL/foo/bar/baz" > /dev/null
|
||||
sleep 1
|
||||
|
||||
local found_api=$(grep -c '"path":"/api/users"' "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
local found_foo=$(grep -c '"path":"/foo/bar/baz"' "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
|
||||
if [ "$found_api" -gt 0 ] && [ "$found_foo" -gt 0 ]; then
|
||||
log_pass "Path logging test"
|
||||
else
|
||||
log_fail "Path logging test - Not all paths found"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main test runner
|
||||
main() {
|
||||
echo "========================================"
|
||||
echo "mod_reqin_log Integration Tests"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
check_prerequisites
|
||||
start_consumer
|
||||
|
||||
# Give Apache time to connect to socket
|
||||
sleep 2
|
||||
|
||||
# Run tests
|
||||
test_basic_logging
|
||||
test_custom_headers
|
||||
test_multiple_headers
|
||||
test_json_format
|
||||
test_required_fields
|
||||
test_method_variations
|
||||
test_path_logging
|
||||
|
||||
# Stop consumer
|
||||
stop_consumer
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "Test Summary"
|
||||
echo "========================================"
|
||||
echo "Tests run: $TESTS_RUN"
|
||||
echo -e "Tests passed: ${GREEN}$TESTS_PASSED${NC}"
|
||||
echo -e "Tests failed: ${RED}$TESTS_FAILED${NC}"
|
||||
echo ""
|
||||
|
||||
if [ $TESTS_FAILED -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}All tests passed!${NC}"
|
||||
exit 0
|
||||
}
|
||||
|
||||
main "$@"
|
||||
185
scripts/socket_consumer.py
Executable file
185
scripts/socket_consumer.py
Executable file
@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
socket_consumer.py - Unix socket consumer for mod_reqin_log
|
||||
|
||||
This script creates a Unix domain socket server that receives JSON log lines
|
||||
from the mod_reqin_log Apache module. It is primarily used for testing and
|
||||
development purposes.
|
||||
|
||||
Usage:
|
||||
python3 socket_consumer.py [socket_path]
|
||||
|
||||
Example:
|
||||
python3 socket_consumer.py /var/run/mod_reqin_log.sock
|
||||
"""
|
||||
|
||||
import socket
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import signal
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
# Default socket path
|
||||
DEFAULT_SOCKET_PATH = "/tmp/mod_reqin_log.sock"
|
||||
|
||||
# Global flag for graceful shutdown
|
||||
shutdown_requested = False
|
||||
|
||||
|
||||
def signal_handler(signum, frame):
|
||||
"""Handle shutdown signals gracefully."""
|
||||
global shutdown_requested
|
||||
shutdown_requested = True
|
||||
print("\nShutdown requested, finishing current operations...")
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""Parse command line arguments."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Unix socket consumer for mod_reqin_log"
|
||||
)
|
||||
parser.add_argument(
|
||||
"socket_path",
|
||||
nargs="?",
|
||||
default=DEFAULT_SOCKET_PATH,
|
||||
help=f"Path to Unix socket (default: {DEFAULT_SOCKET_PATH})"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-q", "--quiet",
|
||||
action="store_true",
|
||||
help="Suppress log output"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o", "--output",
|
||||
type=str,
|
||||
help="Write logs to file instead of stdout"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--validate-json",
|
||||
action="store_true",
|
||||
help="Validate JSON and pretty-print"
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def create_socket(socket_path):
|
||||
"""Create and bind Unix domain socket."""
|
||||
# Remove existing socket file
|
||||
if os.path.exists(socket_path):
|
||||
os.remove(socket_path)
|
||||
|
||||
# Create socket
|
||||
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server.bind(socket_path)
|
||||
server.listen(5)
|
||||
|
||||
# Set permissions (allow Apache to connect)
|
||||
os.chmod(socket_path, 0o666)
|
||||
|
||||
return server
|
||||
|
||||
|
||||
def process_log_line(line, validate_json=False, output_file=None):
|
||||
"""Process a single log line."""
|
||||
line = line.strip()
|
||||
if not line:
|
||||
return
|
||||
|
||||
if validate_json:
|
||||
try:
|
||||
log_entry = json.loads(line)
|
||||
line = json.dumps(log_entry, indent=2)
|
||||
except json.JSONDecodeError as e:
|
||||
line = f"[INVALID JSON] {line}\nError: {e}"
|
||||
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
output = f"[{timestamp}] {line}"
|
||||
|
||||
if output_file:
|
||||
output_file.write(output + "\n")
|
||||
output_file.flush()
|
||||
else:
|
||||
print(output)
|
||||
|
||||
|
||||
def handle_client(conn, validate_json=False, output_file=None):
|
||||
"""Handle a client connection."""
|
||||
data = b""
|
||||
try:
|
||||
while not shutdown_requested:
|
||||
chunk = conn.recv(4096)
|
||||
if not chunk:
|
||||
break
|
||||
|
||||
data += chunk
|
||||
|
||||
# Process complete lines
|
||||
while b"\n" in data:
|
||||
newline_pos = data.index(b"\n")
|
||||
line = data[:newline_pos].decode("utf-8", errors="replace")
|
||||
data = data[newline_pos + 1:]
|
||||
process_log_line(line, validate_json, output_file)
|
||||
except Exception as e:
|
||||
print(f"Error handling client: {e}", file=sys.stderr)
|
||||
finally:
|
||||
# Process any remaining data
|
||||
if data:
|
||||
line = data.decode("utf-8", errors="replace")
|
||||
process_log_line(line, validate_json, output_file)
|
||||
conn.close()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
args = parse_args()
|
||||
|
||||
# Setup signal handlers
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
output_file = None
|
||||
if args.output:
|
||||
output_file = open(args.output, "a")
|
||||
|
||||
try:
|
||||
# Create socket
|
||||
server = create_socket(args.socket_path)
|
||||
print(f"Listening on {args.socket_path}", file=sys.stderr)
|
||||
if not args.quiet:
|
||||
print(f"Waiting for connections... (Ctrl+C to stop)", file=sys.stderr)
|
||||
|
||||
# Accept connections
|
||||
while not shutdown_requested:
|
||||
try:
|
||||
server.settimeout(1.0)
|
||||
try:
|
||||
conn, addr = server.accept()
|
||||
except socket.timeout:
|
||||
continue
|
||||
|
||||
# Handle client in same thread for simplicity
|
||||
handle_client(conn, args.validate_json, output_file)
|
||||
except Exception as e:
|
||||
if not shutdown_requested:
|
||||
print(f"Accept error: {e}", file=sys.stderr)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fatal error: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
finally:
|
||||
# Cleanup
|
||||
if os.path.exists(args.socket_path):
|
||||
os.remove(args.socket_path)
|
||||
if output_file:
|
||||
output_file.close()
|
||||
print("Socket consumer stopped.", file=sys.stderr)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
76
scripts/socket_listener.py
Normal file
76
scripts/socket_listener.py
Normal file
@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
socket_listener.py - Simple Unix socket listener for testing mod_reqin_log
|
||||
Receives JSON log lines and writes them to a file.
|
||||
"""
|
||||
|
||||
import socket
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import argparse
|
||||
|
||||
shutdown_requested = False
|
||||
|
||||
def signal_handler(signum, frame):
|
||||
global shutdown_requested
|
||||
shutdown_requested = True
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Unix socket listener for testing')
|
||||
parser.add_argument('socket_path', help='Path to Unix socket')
|
||||
parser.add_argument('-o', '--output', required=True, help='Output file for logs')
|
||||
args = parser.parse_args()
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
# Remove existing socket
|
||||
if os.path.exists(args.socket_path):
|
||||
os.remove(args.socket_path)
|
||||
|
||||
# Create socket
|
||||
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server.bind(args.socket_path)
|
||||
server.listen(5)
|
||||
os.chmod(args.socket_path, 0o666)
|
||||
|
||||
print(f"Listening on {args.socket_path}", file=sys.stderr)
|
||||
sys.stderr.flush()
|
||||
|
||||
with open(args.output, 'w') as f:
|
||||
while not shutdown_requested:
|
||||
try:
|
||||
server.settimeout(1.0)
|
||||
try:
|
||||
conn, addr = server.accept()
|
||||
print(f"Connection accepted from {addr}", file=sys.stderr)
|
||||
sys.stderr.flush()
|
||||
except socket.timeout:
|
||||
continue
|
||||
|
||||
data = b""
|
||||
while not shutdown_requested:
|
||||
chunk = conn.recv(4096)
|
||||
if not chunk:
|
||||
break
|
||||
data += chunk
|
||||
while b"\n" in data:
|
||||
newline_pos = data.index(b"\n")
|
||||
line = data[:newline_pos].decode("utf-8", errors="replace")
|
||||
data = data[newline_pos + 1:]
|
||||
if line.strip():
|
||||
f.write(line + "\n")
|
||||
f.flush()
|
||||
print(f"Received: {line[:100]}...", file=sys.stderr)
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
|
||||
if os.path.exists(args.socket_path):
|
||||
os.remove(args.socket_path)
|
||||
print("Listener stopped.", file=sys.stderr)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
31
scripts/test.sh
Executable file
31
scripts/test.sh
Executable file
@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# test.sh - Run tests for mod_reqin_log in Docker
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
IMAGE_NAME="mod_reqin_log-build"
|
||||
|
||||
echo "========================================"
|
||||
echo "mod_reqin_log - Test Suite"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# Build image if not exists
|
||||
if ! docker images "$IMAGE_NAME" | grep -q "$IMAGE_NAME"; then
|
||||
echo "Building Docker image first..."
|
||||
"$SCRIPT_DIR/scripts/build.sh"
|
||||
fi
|
||||
|
||||
echo "Running unit tests..."
|
||||
echo ""
|
||||
|
||||
# Run unit tests in container
|
||||
docker run --rm "$IMAGE_NAME" bash -c "cd build/tests && ctest --output-on-failure"
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "Unit tests completed"
|
||||
echo "========================================"
|
||||
317
scripts/test_unix_socket.sh
Executable file
317
scripts/test_unix_socket.sh
Executable file
@ -0,0 +1,317 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# test_unix_socket.sh - Integration test for mod_reqin_log Unix socket logging
|
||||
#
|
||||
# This test verifies that:
|
||||
# 1. The module connects to a Unix socket
|
||||
# 2. HTTP requests generate JSON log entries
|
||||
# 3. Log entries are properly formatted and sent to the socket
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SOCKET_PATH="/tmp/mod_reqin_log_test.sock"
|
||||
LOG_OUTPUT="/tmp/mod_reqin_log_output.jsonl"
|
||||
APACHE_PORT="${APACHE_PORT:-8080}"
|
||||
TIMEOUT=30
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() {
|
||||
echo -e "${YELLOW}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_pass() {
|
||||
echo -e "${GREEN}[PASS]${NC} $1"
|
||||
}
|
||||
|
||||
log_fail() {
|
||||
echo -e "${RED}[FAIL]${NC} $1"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
log_info "Cleaning up..."
|
||||
rm -f "$SOCKET_PATH" "$LOG_OUTPUT"
|
||||
pkill -f "socket_listener.py" 2>/dev/null || true
|
||||
pkill -f "apache.*test" 2>/dev/null || true
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
# Check dependencies
|
||||
check_dependencies() {
|
||||
log_info "Checking dependencies..."
|
||||
if ! command -v curl &> /dev/null; then
|
||||
log_fail "curl is required but not installed"
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
log_fail "python3 is required but not installed"
|
||||
exit 1
|
||||
fi
|
||||
log_pass "Dependencies OK"
|
||||
}
|
||||
|
||||
# Start Unix socket listener that logs received data
|
||||
start_socket_listener() {
|
||||
log_info "Starting Unix socket listener on $SOCKET_PATH..."
|
||||
rm -f "$SOCKET_PATH"
|
||||
|
||||
# Use Python script to listen on Unix socket
|
||||
python3 /build/scripts/socket_listener.py "$SOCKET_PATH" -o "$LOG_OUTPUT" &
|
||||
LISTENER_PID=$!
|
||||
|
||||
sleep 2
|
||||
|
||||
if ! kill -0 $LISTENER_PID 2>/dev/null; then
|
||||
log_fail "Failed to start socket listener"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_pass "Socket listener started (PID: $LISTENER_PID)"
|
||||
}
|
||||
|
||||
# Create Apache test configuration
|
||||
create_apache_config() {
|
||||
log_info "Creating Apache test configuration..."
|
||||
|
||||
cat > /tmp/httpd_test.conf << EOF
|
||||
ServerRoot "/etc/httpd"
|
||||
Listen $APACHE_PORT
|
||||
ServerName localhost
|
||||
|
||||
LoadModule reqin_log_module /build/.libs/mod_reqin_log.so
|
||||
LoadModule mpm_event_module modules/mod_mpm_event.so
|
||||
LoadModule authz_core_module modules/mod_authz_core.so
|
||||
LoadModule dir_module modules/mod_dir.so
|
||||
LoadModule mime_module modules/mod_mime.so
|
||||
LoadModule unixd_module modules/mod_unixd.so
|
||||
LoadModule log_config_module modules/mod_log_config.so
|
||||
|
||||
User apache
|
||||
Group apache
|
||||
|
||||
TypesConfig /etc/mime.types
|
||||
DirectoryIndex index.html
|
||||
|
||||
JsonSockLogEnabled On
|
||||
JsonSockLogSocket "$SOCKET_PATH"
|
||||
JsonSockLogHeaders X-Request-Id User-Agent X-Test-Header
|
||||
JsonSockLogMaxHeaders 10
|
||||
JsonSockLogMaxHeaderValueLen 256
|
||||
JsonSockLogReconnectInterval 5
|
||||
JsonSockLogErrorReportInterval 5
|
||||
|
||||
<VirtualHost *:$APACHE_PORT>
|
||||
DocumentRoot /var/www/html
|
||||
<Directory /var/www/html>
|
||||
Require all granted
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
|
||||
ErrorLog /dev/stderr
|
||||
LogLevel warn
|
||||
EOF
|
||||
|
||||
log_pass "Apache configuration created"
|
||||
}
|
||||
|
||||
# Start Apache with test configuration
|
||||
start_apache() {
|
||||
log_info "Starting Apache with mod_reqin_log..."
|
||||
|
||||
# Create document root if needed
|
||||
mkdir -p /var/www/html
|
||||
echo "<html><body><h1>Test</h1></body></html>" > /var/www/html/index.html
|
||||
|
||||
# Check socket exists
|
||||
if [ ! -S "$SOCKET_PATH" ]; then
|
||||
log_fail "Socket file does not exist: $SOCKET_PATH"
|
||||
ls -la /tmp/*.sock 2>&1 || true
|
||||
exit 1
|
||||
fi
|
||||
log_info "Socket file exists: $SOCKET_PATH"
|
||||
ls -la "$SOCKET_PATH"
|
||||
|
||||
# Start Apache and capture stderr
|
||||
httpd -f /tmp/httpd_test.conf -DFOREGROUND 2>&1 &
|
||||
APACHE_PID=$!
|
||||
|
||||
# Wait for Apache to start
|
||||
sleep 2
|
||||
|
||||
if ! kill -0 $APACHE_PID 2>/dev/null; then
|
||||
log_fail "Failed to start Apache"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_pass "Apache started (PID: $APACHE_PID)"
|
||||
|
||||
# Wait for Apache workers to initialize and connect to socket
|
||||
log_info "Waiting for Apache workers to initialize..."
|
||||
sleep 3
|
||||
}
|
||||
|
||||
# Send test HTTP requests
|
||||
send_test_requests() {
|
||||
log_info "Sending test HTTP requests..."
|
||||
|
||||
# Health check first
|
||||
local retries=5
|
||||
while [ $retries -gt 0 ]; do
|
||||
if curl -s -o /dev/null -w "%{http_code}" "http://localhost:$APACHE_PORT/" | grep -q "200"; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
retries=$((retries - 1))
|
||||
done
|
||||
|
||||
if [ $retries -eq 0 ]; then
|
||||
log_fail "Apache health check failed"
|
||||
return 1
|
||||
fi
|
||||
log_info "Apache health check passed"
|
||||
|
||||
# Request 1: Basic GET
|
||||
curl -s "http://localhost:$APACHE_PORT/" > /dev/null
|
||||
sleep 0.5
|
||||
|
||||
# Request 2: With custom headers
|
||||
curl -s -H "X-Request-Id: test-12345" "http://localhost:$APACHE_PORT/" > /dev/null
|
||||
sleep 0.5
|
||||
|
||||
# Request 3: POST request
|
||||
curl -s -X POST -d "test=data" "http://localhost:$APACHE_PORT/" > /dev/null
|
||||
sleep 0.5
|
||||
|
||||
# Request 4: With User-Agent
|
||||
curl -s -A "TestAgent/1.0" "http://localhost:$APACHE_PORT/api/test" > /dev/null
|
||||
sleep 0.5
|
||||
|
||||
# Request 5: Multiple headers
|
||||
curl -s \
|
||||
-H "X-Request-Id: req-abc" \
|
||||
-H "X-Test-Header: header-value" \
|
||||
-H "User-Agent: Mozilla/5.0" \
|
||||
"http://localhost:$APACHE_PORT/page" > /dev/null
|
||||
sleep 1
|
||||
|
||||
log_pass "Test requests sent"
|
||||
}
|
||||
|
||||
# Verify log output
|
||||
verify_logs() {
|
||||
log_info "Verifying log output..."
|
||||
|
||||
if [ ! -f "$LOG_OUTPUT" ]; then
|
||||
log_fail "Log file not created"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local log_count=$(wc -l < "$LOG_OUTPUT")
|
||||
if [ "$log_count" -lt 1 ]; then
|
||||
log_fail "Expected at least 1 log entry, got $log_count"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_pass "Received $log_count log entries"
|
||||
|
||||
# Show all log entries
|
||||
log_info "Log file contents:"
|
||||
cat "$LOG_OUTPUT"
|
||||
|
||||
# Verify JSON format
|
||||
log_info "Validating JSON format..."
|
||||
local invalid_json=0
|
||||
while IFS= read -r line; do
|
||||
if [ -n "$line" ]; then
|
||||
if ! echo "$line" | python3 -m json.tool > /dev/null 2>&1; then
|
||||
log_fail "Invalid JSON: $line"
|
||||
invalid_json=1
|
||||
fi
|
||||
fi
|
||||
done < "$LOG_OUTPUT"
|
||||
|
||||
if [ $invalid_json -eq 0 ]; then
|
||||
log_pass "All JSON entries are valid"
|
||||
fi
|
||||
|
||||
# Verify required fields
|
||||
log_info "Checking required fields..."
|
||||
local first_line=$(head -1 "$LOG_OUTPUT")
|
||||
|
||||
local required_fields=("time" "timestamp" "src_ip" "dst_ip" "method" "path" "host")
|
||||
local missing_fields=0
|
||||
|
||||
for field in "${required_fields[@]}"; do
|
||||
if ! echo "$first_line" | grep -q "\"$field\":"; then
|
||||
log_fail "Missing required field: $field"
|
||||
missing_fields=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $missing_fields -eq 0 ]; then
|
||||
log_pass "All required fields present"
|
||||
fi
|
||||
|
||||
# Verify header logging
|
||||
log_info "Checking header logging..."
|
||||
if grep -q '"header_X-Request-Id"' "$LOG_OUTPUT"; then
|
||||
log_pass "Custom headers are logged"
|
||||
else
|
||||
log_info "Custom headers test skipped (no X-Request-Id in requests)"
|
||||
fi
|
||||
|
||||
# Verify HTTP methods
|
||||
log_info "Checking HTTP methods..."
|
||||
if grep -q '"method":"GET"' "$LOG_OUTPUT"; then
|
||||
log_pass "GET method logged"
|
||||
fi
|
||||
|
||||
if grep -q '"method":"POST"' "$LOG_OUTPUT"; then
|
||||
log_pass "POST method logged"
|
||||
fi
|
||||
|
||||
# Show sample log entry
|
||||
log_info "Sample log entry (formatted):"
|
||||
head -1 "$LOG_OUTPUT" | python3 -m json.tool 2>/dev/null || head -1 "$LOG_OUTPUT"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Main test runner
|
||||
main() {
|
||||
echo "========================================"
|
||||
echo "mod_reqin_log Unix Socket Integration Test"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
check_dependencies
|
||||
start_socket_listener
|
||||
create_apache_config
|
||||
start_apache
|
||||
send_test_requests
|
||||
|
||||
log_info "Waiting for logs to be written..."
|
||||
sleep 2
|
||||
|
||||
verify_logs
|
||||
local result=$?
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
if [ $result -eq 0 ]; then
|
||||
echo -e "${GREEN}All tests passed!${NC}"
|
||||
else
|
||||
echo -e "${RED}Some tests failed!${NC}"
|
||||
fi
|
||||
echo "========================================"
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user