v0.1.6 changed target check interval per stream, version info added, makefile added

This commit is contained in:
2025-07-07 18:20:46 -04:00
parent 069818bf3d
commit 80180f74a0
14 changed files with 272 additions and 66 deletions

31
Makefile Normal file
View File

@ -0,0 +1,31 @@
# FILE: logwisp/Makefile
BINARY_NAME := logwisp
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BUILD_TIME := $(shell date -u '+%Y-%m-%d_%H:%M:%S')
LDFLAGS := -ldflags "-X 'logwisp/src/internal/version.Version=$(VERSION)' \
-X 'logwisp/src/internal/version.GitCommit=$(GIT_COMMIT)' \
-X 'logwisp/src/internal/version.BuildTime=$(BUILD_TIME)'"
.PHONY: build
build:
go build $(LDFLAGS) -o $(BINARY_NAME) ./src/cmd/logwisp
.PHONY: install
install: build
install -m 755 $(BINARY_NAME) /usr/local/bin/
.PHONY: clean
clean:
rm -f $(BINARY_NAME)
.PHONY: test
test:
go test -v ./...
.PHONY: release
release:
@if [ -z "$(TAG)" ]; then echo "TAG is required: make release TAG=v1.0.0"; exit 1; fi
git tag -a $(TAG) -m "Release $(TAG)"
git push origin $(TAG)

144
README.md
View File

@ -10,21 +10,23 @@ A high-performance log streaming service with multi-stream architecture, support
- **Multi-Stream Architecture**: Run multiple independent log streams, each with its own configuration - **Multi-Stream Architecture**: Run multiple independent log streams, each with its own configuration
- **Dual Protocol Support**: TCP (raw streaming) and HTTP/SSE (browser-friendly) - **Dual Protocol Support**: TCP (raw streaming) and HTTP/SSE (browser-friendly)
- **Real-time Monitoring**: Instant updates with configurable check intervals - **Real-time Monitoring**: Instant updates with per-stream configurable check intervals
- **File Rotation Detection**: Automatic detection and handling of log rotation - **File Rotation Detection**: Automatic detection and handling of log rotation
- **Path-based Routing**: Optional HTTP router for consolidated access - **Path-based Routing**: Optional HTTP router for consolidated access
- **Per-Stream Configuration**: Independent settings for each log stream - **Per-Stream Configuration**: Independent settings including check intervals for each log stream
- **Connection Statistics**: Real-time monitoring of active connections - **Connection Statistics**: Real-time monitoring of active connections
- **Flexible Targets**: Monitor individual files or entire directories - **Flexible Targets**: Monitor individual files or entire directories
- **Zero Dependencies**: Only gnet and fasthttp beyond stdlib - **Version Management**: Git tag-based versioning with build information
- **Configurable Heartbeats**: Keep connections alive with customizable formats
- **Minimal Direct Dependencies**: panjf2000/gnet/v2, valyala/fasthttp, lixenwraith/config, and stdlib
## Quick Start ## Quick Start
```bash ```bash
# Build # Build with version information
go build -o logwisp ./src/cmd/logwisp make build
# Run with default configuration # Run with default configuration if ~/.config/logwisp.toml doesn't exists
./logwisp ./logwisp
# Run with custom config # Run with custom config
@ -32,6 +34,9 @@ go build -o logwisp ./src/cmd/logwisp
# Run with HTTP router (path-based routing) # Run with HTTP router (path-based routing)
./logwisp --router ./logwisp --router
# Show version information
./logwisp --version
``` ```
## Architecture ## Architecture
@ -57,15 +62,13 @@ Configuration file location: `~/.config/logwisp.toml`
### Basic Multi-Stream Configuration ### Basic Multi-Stream Configuration
```toml ```toml
# Global defaults
[monitor]
check_interval_ms = 100
# Application logs stream # Application logs stream
[[streams]] [[streams]]
name = "app" name = "app"
[streams.monitor] [streams.monitor]
# Per-stream check interval in milliseconds
check_interval_ms = 100
targets = [ targets = [
{ path = "/var/log/myapp", pattern = "*.log", is_file = false }, { path = "/var/log/myapp", pattern = "*.log", is_file = false },
{ path = "/var/log/myapp/app.log", is_file = true } { path = "/var/log/myapp/app.log", is_file = true }
@ -78,12 +81,21 @@ buffer_size = 2000
stream_path = "/stream" stream_path = "/stream"
status_path = "/status" status_path = "/status"
# System logs stream # Heartbeat configuration
[streams.httpserver.heartbeat]
enabled = true
interval_seconds = 30
format = "comment" # or "json" for structured events
include_timestamp = true
include_stats = false
# System logs stream with slower check interval
[[streams]] [[streams]]
name = "system" name = "system"
[streams.monitor] [streams.monitor]
check_interval_ms = 50 # Override global default # Check every 60 seconds for slowly updating logs
check_interval_ms = 60000
targets = [ targets = [
{ path = "/var/log/syslog", is_file = true }, { path = "/var/log/syslog", is_file = true },
{ path = "/var/log/auth.log", is_file = true } { path = "/var/log/auth.log", is_file = true }
@ -94,11 +106,12 @@ enabled = true
port = 9090 port = 9090
buffer_size = 5000 buffer_size = 5000
[streams.httpserver] # TCP heartbeat (always JSON format)
[streams.tcpserver.heartbeat]
enabled = true enabled = true
port = 8443 interval_seconds = 300 # 5 minutes
stream_path = "/logs" include_timestamp = true
status_path = "/health" include_stats = true
``` ```
### Target Configuration ### Target Configuration
@ -116,6 +129,42 @@ Monitor targets support both files and directories:
{ path = "./logs", pattern = "*.log", is_file = false } { path = "./logs", pattern = "*.log", is_file = false }
``` ```
### Check Interval Configuration
Each stream can have its own check interval based on log update frequency:
- **High-frequency logs**: 50-100ms (e.g., application debug logs)
- **Normal logs**: 100-1000ms (e.g., application logs)
- **Low-frequency logs**: 10000-60000ms (e.g., system logs, archives)
### Heartbeat Configuration
Keep connections alive and detect stale clients with configurable heartbeats:
```toml
[streams.httpserver.heartbeat]
enabled = true
interval_seconds = 30
format = "comment" # "comment" for SSE comments, "json" for events
include_timestamp = true # Add timestamp to heartbeat
include_stats = true # Include connection count and uptime
```
**Heartbeat Formats**:
Comment format (SSE):
```
: heartbeat 2025-01-07T10:30:00Z clients=5 uptime=3600s
```
JSON format (SSE):
```
event: heartbeat
data: {"type":"heartbeat","timestamp":"2025-01-07T10:30:00Z","active_clients":5,"uptime_seconds":3600}
```
TCP always uses JSON format with newline delimiter.
## Usage Modes ## Usage Modes
### 1. Standalone Mode (Default) ### 1. Standalone Mode (Default)
@ -183,6 +232,11 @@ eventSource.addEventListener('message', (e) => {
const logEntry = JSON.parse(e.data); const logEntry = JSON.parse(e.data);
console.log(`[${logEntry.time}] ${logEntry.level}: ${logEntry.message}`); console.log(`[${logEntry.time}] ${logEntry.level}: ${logEntry.message}`);
}); });
eventSource.addEventListener('heartbeat', (e) => {
const heartbeat = JSON.parse(e.data);
console.log('Heartbeat:', heartbeat);
});
``` ```
## Log Entry Format ## Log Entry Format
@ -219,7 +273,7 @@ All log entries are streamed as JSON:
```json ```json
{ {
"service": "LogWisp", "service": "LogWisp",
"version": "3.0.0", "version": "v1.0.0",
"server": { "server": {
"type": "http", "type": "http",
"port": 8080, "port": 8080,
@ -274,24 +328,26 @@ When rotation is detected, a special log entry is generated:
- **Per-client buffers**: Each client has independent buffer space - **Per-client buffers**: Each client has independent buffer space
- **Configurable sizes**: Adjust buffer sizes based on expected load - **Configurable sizes**: Adjust buffer sizes based on expected load
### Heartbeat Messages ### Per-Stream Check Intervals
Keep connections alive and detect stale clients: Optimize resource usage by configuring check intervals based on log update frequency:
```toml ```toml
[streams.httpserver.heartbeat] # High-frequency application logs
enabled = true [streams.monitor]
interval_seconds = 30 check_interval_ms = 50 # Check every 50ms
include_timestamp = true
include_stats = true # Low-frequency system logs
format = "json" # or "comment" for SSE comments [streams.monitor]
check_interval_ms = 60000 # Check every minute
``` ```
## Performance Tuning ## Performance Tuning
### Monitor Settings ### Monitor Settings
- `check_interval_ms`: Lower values = faster detection, higher CPU usage - `check_interval_ms`: Lower values = faster detection, higher CPU usage
- `buffer_size`: Larger buffers handle bursts better but use more memory - Configure per-stream based on expected update frequency
- Use 10000ms+ for archival or slowly updating logs
### File Watcher Optimization ### File Watcher Optimization
- Use specific file paths when possible (more efficient than directory scanning) - Use specific file paths when possible (more efficient than directory scanning)
@ -307,7 +363,7 @@ format = "json" # or "comment" for SSE comments
```bash ```bash
# Clone repository # Clone repository
git clone https://github.com/yourusername/logwisp git clone https://github.com/lixenwraith/logwisp
cd logwisp cd logwisp
# Install dependencies # Install dependencies
@ -316,13 +372,24 @@ go get github.com/panjf2000/gnet/v2
go get github.com/valyala/fasthttp go get github.com/valyala/fasthttp
go get github.com/lixenwraith/config go get github.com/lixenwraith/config
# Build # Build with version information
go build -o logwisp ./src/cmd/logwisp make build
# Run tests # Run tests
go test ./... make test
# Create a release
make release TAG=v1.0.0
``` ```
### Makefile Targets
- `make build` - Build binary with version information
- `make install` - Install to /usr/local/bin
- `make clean` - Remove built binary
- `make test` - Run test suite
- `make release TAG=vX.Y.Z` - Create and push git tag
## Deployment ## Deployment
### Systemd Service ### Systemd Service
@ -356,7 +423,7 @@ WantedBy=multi-user.target
FROM golang:1.24 AS builder FROM golang:1.24 AS builder
WORKDIR /app WORKDIR /app
COPY . . COPY . .
RUN go build -o logwisp ./src/cmd/logwisp RUN make build
FROM debian:bookworm-slim FROM debian:bookworm-slim
RUN useradd -r -s /bin/false logwisp RUN useradd -r -s /bin/false logwisp
@ -411,6 +478,7 @@ services:
2. Verify file paths in configuration 2. Verify file paths in configuration
3. Ensure files match the specified patterns 3. Ensure files match the specified patterns
4. Check monitor statistics in status endpoint 4. Check monitor statistics in status endpoint
5. Verify check_interval_ms is appropriate for log update frequency
### High Memory Usage ### High Memory Usage
1. Reduce buffer sizes in configuration 1. Reduce buffer sizes in configuration
@ -424,6 +492,12 @@ services:
3. Monitor client-side errors 3. Monitor client-side errors
4. Review dropped entry statistics 4. Review dropped entry statistics
### Version Information
Use `./logwisp --version` to see:
- Version tag (from git tags)
- Git commit hash
- Build timestamp
## License ## License
BSD-3-Clause BSD-3-Clause
@ -438,9 +512,13 @@ Contributions are welcome! Please read our contributing guidelines and submit pu
- [x] File and directory monitoring - [x] File and directory monitoring
- [x] TCP and HTTP/SSE streaming - [x] TCP and HTTP/SSE streaming
- [x] Path-based HTTP routing - [x] Path-based HTTP routing
- [x] Per-stream check intervals
- [x] Version management
- [x] Configurable heartbeats
- [ ] Rate and connection limiting
- [ ] Log filtering and transformation
- [ ] Configurable logging support
- [ ] Authentication (Basic, JWT, mTLS) - [ ] Authentication (Basic, JWT, mTLS)
- [ ] TLS/SSL support - [ ] TLS/SSL support
- [ ] Rate limiting
- [ ] Prometheus metrics export - [ ] Prometheus metrics export
- [ ] WebSocket support - [ ] WebSocket support
- [ ] Log filtering and transformation

View File

@ -1,15 +1,15 @@
# LogWisp Multi-Stream Configuration # LogWisp Multi-Stream Configuration
# Location: ~/.config/logwisp.toml # Location: ~/.config/logwisp.toml
# Global monitor defaults
[monitor]
check_interval_ms = 100
# Stream 1: Application logs (public access) # Stream 1: Application logs (public access)
[[streams]] [[streams]]
name = "app" name = "app"
[streams.monitor] [streams.monitor]
# Check interval in milliseconds (per-stream configuration)
check_interval_ms = 100
# Array of folders and files to be monitored
# For file targets, pattern is ignored and can be omitted
targets = [ targets = [
{ path = "/var/log/myapp", pattern = "*.log", is_file = false }, { path = "/var/log/myapp", pattern = "*.log", is_file = false },
{ path = "/var/log/myapp/app.log", pattern = "", is_file = true } { path = "/var/log/myapp/app.log", pattern = "", is_file = true }
@ -22,17 +22,24 @@ buffer_size = 2000
stream_path = "/stream" stream_path = "/stream"
status_path = "/status" status_path = "/status"
# HTTP SSE Heartbeat Configuration
[streams.httpserver.heartbeat] [streams.httpserver.heartbeat]
enabled = true enabled = true
interval_seconds = 30 interval_seconds = 30
# Format options: "comment" (SSE comments) or "json" (JSON events)
format = "comment" format = "comment"
# Include timestamp in heartbeat
include_timestamp = true
# Include server statistics (client count, uptime)
include_stats = false
# Stream 2: System logs (authenticated) # Stream 2: System logs (authenticated)
[[streams]] [[streams]]
name = "system" name = "system"
[streams.monitor] [streams.monitor]
check_interval_ms = 50 # More frequent checks # More frequent checks for critical system logs
check_interval_ms = 50
targets = [ targets = [
{ path = "/var/log", pattern = "syslog*", is_file = false }, { path = "/var/log", pattern = "syslog*", is_file = false },
{ path = "/var/log/auth.log", pattern = "", is_file = true } { path = "/var/log/auth.log", pattern = "", is_file = true }
@ -45,6 +52,14 @@ buffer_size = 5000
stream_path = "/logs" stream_path = "/logs"
status_path = "/health" status_path = "/health"
# JSON format heartbeat with full stats
[streams.httpserver.heartbeat]
enabled = true
interval_seconds = 20
format = "json"
include_timestamp = true
include_stats = true
# SSL placeholder # SSL placeholder
[streams.httpserver.ssl] [streams.httpserver.ssl]
enabled = true enabled = true
@ -69,14 +84,20 @@ enabled = true
port = 9443 port = 9443
buffer_size = 5000 buffer_size = 5000
# TCP heartbeat (always JSON format)
[streams.tcpserver.heartbeat] [streams.tcpserver.heartbeat]
enabled = false enabled = true
interval_seconds = 60
include_timestamp = true
include_stats = true
# Stream 3: Debug logs (high-volume, no heartbeat) # Stream 3: Debug logs (high-volume, less frequent checks)
[[streams]] [[streams]]
name = "debug" name = "debug"
[streams.monitor] [streams.monitor]
# Check every 10 seconds for debug logs
check_interval_ms = 10000
targets = [ targets = [
{ path = "./debug", pattern = "*.debug", is_file = false } { path = "./debug", pattern = "*.debug", is_file = false }
] ]
@ -88,8 +109,9 @@ buffer_size = 10000
stream_path = "/stream" stream_path = "/stream"
status_path = "/status" status_path = "/status"
# Disable heartbeat for high-volume stream
[streams.httpserver.heartbeat] [streams.httpserver.heartbeat]
enabled = false # Disable for high-volume enabled = false
# Rate limiting placeholder # Rate limiting placeholder
[streams.httpserver.rate_limit] [streams.httpserver.rate_limit]
@ -98,6 +120,40 @@ requests_per_second = 100.0
burst_size = 1000 burst_size = 1000
limit_by = "ip" limit_by = "ip"
# Stream 4: Slow logs (infrequent updates)
[[streams]]
name = "archive"
[streams.monitor]
# Check once per minute for archival logs
check_interval_ms = 60000
targets = [
{ path = "/var/log/archive", pattern = "*.log.gz", is_file = false }
]
[streams.tcpserver]
enabled = true
port = 9091
buffer_size = 1000
# Minimal heartbeat for connection keep-alive
[streams.tcpserver.heartbeat]
enabled = true
interval_seconds = 300 # 5 minutes
include_timestamp = false
include_stats = false
# Heartbeat Format Examples:
#
# Comment format (SSE):
# : heartbeat 2025-01-07T10:30:00Z clients=5 uptime=3600s
#
# JSON format (SSE):
# event: heartbeat
# data: {"type":"heartbeat","timestamp":"2025-01-07T10:30:00Z","active_clients":5,"uptime_seconds":3600}
#
# TCP always uses JSON format with newline delimiter
# Usage Examples: # Usage Examples:
# #
# 1. Standard mode (each stream on its own port): # 1. Standard mode (each stream on its own port):
@ -105,6 +161,7 @@ limit_by = "ip"
# - App logs: http://localhost:8080/stream # - App logs: http://localhost:8080/stream
# - System logs: https://localhost:8443/logs (with auth) # - System logs: https://localhost:8443/logs (with auth)
# - Debug logs: http://localhost:8082/stream # - Debug logs: http://localhost:8082/stream
# - Archive logs: tcp://localhost:9091
# #
# 2. Router mode (shared port with path routing): # 2. Router mode (shared port with path routing):
# ./logwisp --router # ./logwisp --router
@ -117,5 +174,8 @@ limit_by = "ip"
# ./logwisp --config /etc/logwisp/production.toml # ./logwisp --config /etc/logwisp/production.toml
# #
# 4. Environment variables: # 4. Environment variables:
# LOGWISP_MONITOR_CHECK_INTERVAL_MS=50 # LOGWISP_STREAMS_0_MONITOR_CHECK_INTERVAL_MS=50
# LOGWISP_STREAMS_0_HTTPSERVER_PORT=8090 # LOGWISP_STREAMS_0_HTTPSERVER_PORT=8090
#
# 5. Show version:
# ./logwisp --version

View File

@ -12,6 +12,7 @@ import (
"logwisp/src/internal/config" "logwisp/src/internal/config"
"logwisp/src/internal/logstream" "logwisp/src/internal/logstream"
"logwisp/src/internal/version"
) )
func main() { func main() {
@ -20,9 +21,15 @@ func main() {
configFile = flag.String("config", "", "Config file path") configFile = flag.String("config", "", "Config file path")
useRouter = flag.Bool("router", false, "Use HTTP router for path-based routing") useRouter = flag.Bool("router", false, "Use HTTP router for path-based routing")
// routerPort = flag.Int("router-port", 0, "Override router port (default: first HTTP port)") // routerPort = flag.Int("router-port", 0, "Override router port (default: first HTTP port)")
showVersion = flag.Bool("version", false, "Show version information")
) )
flag.Parse() flag.Parse()
if *showVersion {
fmt.Println(version.String())
os.Exit(0)
}
if *configFile != "" { if *configFile != "" {
os.Setenv("LOGWISP_CONFIG_FILE", *configFile) os.Setenv("LOGWISP_CONFIG_FILE", *configFile)
} }
@ -101,6 +108,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
fmt.Printf("LogWisp %s\n", version.Short())
fmt.Printf("\n%d stream(s) running. Press Ctrl+C to stop.\n", successCount) fmt.Printf("\n%d stream(s) running. Press Ctrl+C to stop.\n", successCount)
// Start periodic status display // Start periodic status display

View File

@ -2,9 +2,6 @@
package config package config
type Config struct { type Config struct {
// Global monitor settings
Monitor MonitorConfig `toml:"monitor"`
// Stream configurations // Stream configurations
Streams []StreamConfig `toml:"streams"` Streams []StreamConfig `toml:"streams"`
} }

View File

@ -3,21 +3,20 @@ package config
import ( import (
"fmt" "fmt"
lconfig "github.com/lixenwraith/config"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
lconfig "github.com/lixenwraith/config"
) )
func defaults() *Config { func defaults() *Config {
return &Config{ return &Config{
Monitor: MonitorConfig{
CheckIntervalMs: 100,
},
Streams: []StreamConfig{ Streams: []StreamConfig{
{ {
Name: "default", Name: "default",
Monitor: &StreamMonitorConfig{ Monitor: &StreamMonitorConfig{
CheckIntervalMs: 100,
Targets: []MonitorTarget{ Targets: []MonitorTarget{
{Path: "./", Pattern: "*.log", IsFile: false}, {Path: "./", Pattern: "*.log", IsFile: false},
}, },

View File

@ -17,7 +17,7 @@ type StreamConfig struct {
} }
type StreamMonitorConfig struct { type StreamMonitorConfig struct {
CheckIntervalMs *int `toml:"check_interval_ms"` CheckIntervalMs int `toml:"check_interval_ms"`
Targets []MonitorTarget `toml:"targets"` Targets []MonitorTarget `toml:"targets"`
} }
@ -35,8 +35,8 @@ func (s *StreamConfig) GetTargets(defaultTargets []MonitorTarget) []MonitorTarge
} }
func (s *StreamConfig) GetCheckInterval(defaultInterval int) int { func (s *StreamConfig) GetCheckInterval(defaultInterval int) int {
if s.Monitor != nil && s.Monitor.CheckIntervalMs != nil { if s.Monitor != nil && s.Monitor.CheckIntervalMs > 0 {
return *s.Monitor.CheckIntervalMs return s.Monitor.CheckIntervalMs
} }
return defaultInterval return defaultInterval
} }

View File

@ -7,10 +7,6 @@ import (
) )
func (c *Config) validate() error { func (c *Config) validate() error {
if c.Monitor.CheckIntervalMs < 10 {
return fmt.Errorf("check interval too small: %d ms", c.Monitor.CheckIntervalMs)
}
if len(c.Streams) == 0 { if len(c.Streams) == 0 {
return fmt.Errorf("no streams configured") return fmt.Errorf("no streams configured")
} }
@ -29,11 +25,17 @@ func (c *Config) validate() error {
} }
streamNames[stream.Name] = true streamNames[stream.Name] = true
// Stream must have targets // Stream must have monitor config with targets
if stream.Monitor == nil || len(stream.Monitor.Targets) == 0 { if stream.Monitor == nil || len(stream.Monitor.Targets) == 0 {
return fmt.Errorf("stream '%s': no monitor targets specified", stream.Name) return fmt.Errorf("stream '%s': no monitor targets specified", stream.Name)
} }
// Validate check interval
if stream.Monitor.CheckIntervalMs < 10 {
return fmt.Errorf("stream '%s': check interval too small: %d ms (min: 10ms)",
stream.Name, stream.Monitor.CheckIntervalMs)
}
for j, target := range stream.Monitor.Targets { for j, target := range stream.Monitor.Targets {
if target.Path == "" { if target.Path == "" {
return fmt.Errorf("stream '%s' target %d: empty path", stream.Name, j) return fmt.Errorf("stream '%s' target %d: empty path", stream.Name, j)

View File

@ -4,9 +4,10 @@ package logstream
import ( import (
"context" "context"
"fmt" "fmt"
"logwisp/src/internal/config"
"sync" "sync"
"time" "time"
"logwisp/src/internal/config"
) )
func (ls *LogStream) Shutdown() { func (ls *LogStream) Shutdown() {

View File

@ -4,9 +4,11 @@ package logstream
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/valyala/fasthttp"
"strings" "strings"
"sync" "sync"
"github.com/valyala/fasthttp"
"logwisp/src/internal/version"
) )
type routerServer struct { type routerServer struct {
@ -81,6 +83,7 @@ func (rs *routerServer) handleGlobalStatus(ctx *fasthttp.RequestCtx) {
status := map[string]interface{}{ status := map[string]interface{}{
"service": "LogWisp Router", "service": "LogWisp Router",
"version": version.Short(),
"port": rs.port, "port": rs.port,
"streams": streams, "streams": streams,
"total_streams": len(streams), "total_streams": len(streams),

View File

@ -14,6 +14,7 @@ import (
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"logwisp/src/internal/config" "logwisp/src/internal/config"
"logwisp/src/internal/monitor" "logwisp/src/internal/monitor"
"logwisp/src/internal/version"
) )
type HTTPStreamer struct { type HTTPStreamer struct {
@ -286,7 +287,7 @@ func (h *HTTPStreamer) handleStatus(ctx *fasthttp.RequestCtx) {
status := map[string]interface{}{ status := map[string]interface{}{
"service": "LogWisp", "service": "LogWisp",
"version": "3.0.0", "version": version.Short(),
"server": map[string]interface{}{ "server": map[string]interface{}{
"type": "http", "type": "http",
"port": h.config.Port, "port": h.config.Port,
@ -318,17 +319,17 @@ func (h *HTTPStreamer) handleStatus(ctx *fasthttp.RequestCtx) {
ctx.SetBody(data) ctx.SetBody(data)
} }
// GetActiveConnections returns the current number of active clients // Returns the current number of active clients
func (h *HTTPStreamer) GetActiveConnections() int32 { func (h *HTTPStreamer) GetActiveConnections() int32 {
return h.activeClients.Load() return h.activeClients.Load()
} }
// GetStreamPath returns the configured stream endpoint path // Returns the configured stream endpoint path
func (h *HTTPStreamer) GetStreamPath() string { func (h *HTTPStreamer) GetStreamPath() string {
return h.streamPath return h.streamPath
} }
// GetStatusPath returns the configured status endpoint path // Returns the configured status endpoint path
func (h *HTTPStreamer) GetStatusPath() string { func (h *HTTPStreamer) GetStatusPath() string {
return h.statusPath return h.statusPath
} }

View File

@ -3,8 +3,9 @@ package stream
import ( import (
"fmt" "fmt"
"github.com/panjf2000/gnet/v2"
"sync" "sync"
"github.com/panjf2000/gnet/v2"
) )
type tcpServer struct { type tcpServer struct {

View File

@ -146,6 +146,7 @@ func (t *TCPStreamer) formatHeartbeat() []byte {
data["uptime_seconds"] = int(time.Since(t.startTime).Seconds()) data["uptime_seconds"] = int(time.Since(t.startTime).Seconds())
} }
// For TCP, always use JSON format
jsonData, _ := json.Marshal(data) jsonData, _ := json.Marshal(data)
return append(jsonData, '\n') return append(jsonData, '\n')
} }

View File

@ -0,0 +1,24 @@
// FILE: src/internal/version/version.go
package version
import "fmt"
var (
// Version is set at compile time via -ldflags
Version = "dev"
GitCommit = "unknown"
BuildTime = "unknown"
)
// returns a formatted version string
func String() string {
if Version == "dev" {
return fmt.Sprintf("dev (commit: %s, built: %s)", GitCommit, BuildTime)
}
return fmt.Sprintf("%s (commit: %s, built: %s)", Version, GitCommit, BuildTime)
}
// returns just the version tag
func Short() string {
return Version
}