e1.4.4 Fix module path and dependency issues, compatibility package added.
This commit is contained in:
75
example/fasthttp/main.go
Normal file
75
example/fasthttp/main.go
Normal file
@ -0,0 +1,75 @@
|
||||
// FILE: examples/fasthttp/main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lixenwraith/log"
|
||||
"github.com/lixenwraith/log/compat"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create and configure logger
|
||||
logger := log.NewLogger()
|
||||
err := logger.InitWithDefaults(
|
||||
"directory=/var/log/fasthttp",
|
||||
"level=0",
|
||||
"format=txt",
|
||||
"buffer_size=2048",
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer logger.Shutdown()
|
||||
|
||||
// Create fasthttp adapter with custom level detection
|
||||
fasthttpAdapter := compat.NewFastHTTPAdapter(
|
||||
logger,
|
||||
compat.WithDefaultLevel(log.LevelInfo),
|
||||
compat.WithLevelDetector(customLevelDetector),
|
||||
)
|
||||
|
||||
// Configure fasthttp server
|
||||
server := &fasthttp.Server{
|
||||
Handler: requestHandler,
|
||||
Logger: fasthttpAdapter,
|
||||
|
||||
// Other server settings
|
||||
Name: "MyServer",
|
||||
Concurrency: fasthttp.DefaultConcurrency,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
TCPKeepalive: true,
|
||||
ReduceMemoryUsage: true,
|
||||
}
|
||||
|
||||
// Start server
|
||||
fmt.Println("Starting server on :8080")
|
||||
if err := server.ListenAndServe(":8080"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func requestHandler(ctx *fasthttp.RequestCtx) {
|
||||
ctx.SetContentType("text/plain")
|
||||
fmt.Fprintf(ctx, "Hello, world! Path: %s\n", ctx.Path())
|
||||
}
|
||||
|
||||
func customLevelDetector(msg string) int64 {
|
||||
// Custom logic to detect log levels
|
||||
// Can inspect specific fasthttp message patterns
|
||||
|
||||
if strings.Contains(msg, "connection cannot be served") {
|
||||
return log.LevelWarn
|
||||
}
|
||||
if strings.Contains(msg, "error when serving connection") {
|
||||
return log.LevelError
|
||||
}
|
||||
|
||||
// Use default detection
|
||||
return compat.detectLogLevel(msg)
|
||||
}
|
||||
47
example/gnet/main.go
Normal file
47
example/gnet/main.go
Normal file
@ -0,0 +1,47 @@
|
||||
// FILE: example/gnet/main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/lixenwraith/log"
|
||||
"github.com/lixenwraith/log/compat"
|
||||
"github.com/panjf2000/gnet/v2"
|
||||
)
|
||||
|
||||
// Example gnet event handler
|
||||
type echoServer struct {
|
||||
gnet.BuiltinEventEngine
|
||||
}
|
||||
|
||||
func (es *echoServer) OnTraffic(c gnet.Conn) gnet.Action {
|
||||
buf, _ := c.Next(-1)
|
||||
c.Write(buf)
|
||||
return gnet.None
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Method 1: Simple adapter
|
||||
logger := log.NewLogger()
|
||||
err := logger.InitWithDefaults(
|
||||
"directory=/var/log/gnet",
|
||||
"level=-4", // Debug level
|
||||
"format=json",
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer logger.Shutdown()
|
||||
|
||||
gnetAdapter := compat.NewGnetAdapter(logger)
|
||||
|
||||
// Configure gnet server with the logger
|
||||
err = gnet.Run(
|
||||
&echoServer{},
|
||||
"tcp://127.0.0.1:9000",
|
||||
gnet.WithMulticore(true),
|
||||
gnet.WithLogger(gnetAdapter),
|
||||
gnet.WithReusePort(true),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
81
example/heartbeat/main.go
Normal file
81
example/heartbeat/main.go
Normal file
@ -0,0 +1,81 @@
|
||||
// FILE: example/heartbeat/main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/lixenwraith/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create test log directory if it doesn't exist
|
||||
if err := os.MkdirAll("./logs", 0755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create test logs directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Test cycle: disable -> PROC -> PROC+DISK -> PROC+DISK+SYS -> PROC+DISK -> PROC -> disable
|
||||
levels := []struct {
|
||||
level int64
|
||||
description string
|
||||
}{
|
||||
{0, "Heartbeats disabled"},
|
||||
{1, "PROC heartbeats only"},
|
||||
{2, "PROC+DISK heartbeats"},
|
||||
{3, "PROC+DISK+SYS heartbeats"},
|
||||
{2, "PROC+DISK heartbeats (reducing from 3)"},
|
||||
{1, "PROC heartbeats only (reducing from 2)"},
|
||||
{0, "Heartbeats disabled (final)"},
|
||||
}
|
||||
|
||||
// Create a single logger instance that we'll reconfigure
|
||||
logger := log.NewLogger()
|
||||
|
||||
for _, levelConfig := range levels {
|
||||
// Set up configuration overrides
|
||||
overrides := []string{
|
||||
"directory=./logs",
|
||||
"level=-4", // Debug level to see everything
|
||||
"format=txt", // Use text format for easier reading
|
||||
"heartbeat_interval_s=5", // Short interval for testing
|
||||
fmt.Sprintf("heartbeat_level=%d", levelConfig.level),
|
||||
}
|
||||
|
||||
// Initialize logger with the new configuration
|
||||
// Note: InitWithDefaults handles reconfiguration of an existing logger
|
||||
if err := logger.InitWithDefaults(overrides...); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to initialize logger: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Log the current test state
|
||||
fmt.Printf("\n--- Testing heartbeat level %d: %s ---\n", levelConfig.level, levelConfig.description)
|
||||
logger.Info("Heartbeat test started", "level", levelConfig.level, "description", levelConfig.description)
|
||||
|
||||
// Generate some logs to trigger heartbeat counters
|
||||
for j := 0; j < 10; j++ {
|
||||
logger.Debug("Debug test log", "iteration", j, "level_test", levelConfig.level)
|
||||
logger.Info("Info test log", "iteration", j, "level_test", levelConfig.level)
|
||||
logger.Warn("Warning test log", "iteration", j, "level_test", levelConfig.level)
|
||||
logger.Error("Error test log", "iteration", j, "level_test", levelConfig.level)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Wait for heartbeats to generate (slightly longer than the interval)
|
||||
waitTime := 6 * time.Second
|
||||
fmt.Printf("Waiting %v for heartbeats to generate...\n", waitTime)
|
||||
time.Sleep(waitTime)
|
||||
|
||||
logger.Info("Heartbeat test completed for level", "level", levelConfig.level)
|
||||
}
|
||||
|
||||
// Final shutdown
|
||||
if err := logger.Shutdown(2 * time.Second); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to shut down logger: %v\n", err)
|
||||
}
|
||||
|
||||
fmt.Println("\nHeartbeat test program completed successfully")
|
||||
fmt.Println("Check logs directory for generated log files")
|
||||
}
|
||||
58
example/reconfig/main.go
Normal file
58
example/reconfig/main.go
Normal file
@ -0,0 +1,58 @@
|
||||
// FILE: example/reconfig/main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/lixenwraith/log"
|
||||
)
|
||||
|
||||
// Simulate rapid reconfiguration
|
||||
func main() {
|
||||
var count atomic.Int64
|
||||
|
||||
logger := log.NewLogger()
|
||||
|
||||
// Initialize the logger with defaults first
|
||||
err := logger.InitWithDefaults()
|
||||
if err != nil {
|
||||
fmt.Printf("Initial Init error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Log something constantly
|
||||
go func() {
|
||||
for i := 0; ; i++ {
|
||||
logger.Info("Test log", i)
|
||||
count.Add(1)
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
// Trigger multiple reconfigurations rapidly
|
||||
for i := 0; i < 10; i++ {
|
||||
// Use different buffer sizes to trigger channel recreation
|
||||
bufSize := fmt.Sprintf("buffer_size=%d", 100*(i+1))
|
||||
err := logger.InitWithDefaults(bufSize)
|
||||
if err != nil {
|
||||
fmt.Printf("Init error: %v\n", err)
|
||||
}
|
||||
// Minimal delay between reconfigurations
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Check if we see any inconsistency
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
fmt.Printf("Total logger. attempted: %d\n", count.Load())
|
||||
|
||||
// Gracefully shut down the logger.er
|
||||
err = logger.Shutdown(time.Second)
|
||||
if err != nil {
|
||||
fmt.Printf("Shutdown error: %v\n", err)
|
||||
}
|
||||
|
||||
// Check for any error messages in the logger.files
|
||||
// or dropped logger.count
|
||||
}
|
||||
118
example/simple/main.go
Normal file
118
example/simple/main.go
Normal file
@ -0,0 +1,118 @@
|
||||
// FILE: example/simple/main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/lixenwraith/config"
|
||||
"github.com/lixenwraith/log"
|
||||
)
|
||||
|
||||
const configFile = "simple_config.toml"
|
||||
const configBasePath = "logging" // Base path for log settings in config
|
||||
|
||||
// Example TOML content
|
||||
var tomlContent = `
|
||||
# Example simple_config.toml
|
||||
[logging]
|
||||
level = -4 # Debug
|
||||
directory = "./logs"
|
||||
format = "txt"
|
||||
extension = "log"
|
||||
show_timestamp = true
|
||||
show_level = true
|
||||
buffer_size = 1024
|
||||
flush_interval_ms = 100
|
||||
trace_depth = 0
|
||||
retention_period_hrs = 0.0
|
||||
retention_check_mins = 60.0
|
||||
# Other settings use defaults registered by log.Init
|
||||
`
|
||||
|
||||
func main() {
|
||||
fmt.Println("--- Simple Logger Example ---")
|
||||
|
||||
// --- Setup Config ---
|
||||
// Create dummy config file
|
||||
err := os.WriteFile(configFile, []byte(tomlContent), 0644)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write dummy config: %v\n", err)
|
||||
// Continue with defaults potentially
|
||||
} else {
|
||||
fmt.Printf("Created dummy config file: %s\n", configFile)
|
||||
// defer os.Remove(configFile) // Remove to keep the saved config file
|
||||
// defer os.RemoveAll(logsDir) // Remove to keep the log directory
|
||||
}
|
||||
|
||||
// Initialize the external config manager
|
||||
cfg := config.New()
|
||||
|
||||
// Load config from file (and potentially CLI args - none provided here)
|
||||
// The log package will register its keys during Init
|
||||
err = cfg.Load(configFile, nil) // os.Args[1:] could be used here
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to load config: %v. Using defaults.\n", err)
|
||||
// Proceeding, log.Init will use registered defaults
|
||||
}
|
||||
|
||||
// --- Initialize Logger ---
|
||||
logger := log.NewLogger()
|
||||
// Pass the config instance and the base path for logger settings
|
||||
err = logger.Init(cfg, configBasePath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to initialize logger.er: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("Logger initialized.")
|
||||
|
||||
// --- SAVE CONFIGURATION ---
|
||||
// Save the config state *after* logger.Init has registered its keys/defaults
|
||||
// This will write the merged configuration (defaults + file overrides) back.
|
||||
err = cfg.Save(configFile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to save configuration to '%s': %v\n", configFile, err)
|
||||
} else {
|
||||
fmt.Printf("Configuration saved to: %s\n", configFile)
|
||||
}
|
||||
// --- End Save Configuration ---
|
||||
|
||||
// --- Logging ---
|
||||
logger.Debug("This is a debug message.", "user_id", 123)
|
||||
logger.Info("Application starting...")
|
||||
logger.Warn("Potential issue detected.", "threshold", 0.95)
|
||||
logger.Error("An error occurred!", "code", 500)
|
||||
|
||||
// Logging from goroutines
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 2; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
logger.Info("Goroutine started", "id", id)
|
||||
time.Sleep(time.Duration(50+id*50) * time.Millisecond)
|
||||
logger.InfoTrace(1, "Goroutine finished", "id", id) // Log with trace
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Wait for goroutines to finish before shutting down logger.er
|
||||
wg.Wait()
|
||||
fmt.Println("Goroutines finished.")
|
||||
|
||||
// --- Shutdown Logger ---
|
||||
fmt.Println("Shutting down logger.er...")
|
||||
// Provide a reasonable timeout for logger. to flush
|
||||
shutdownTimeout := 2 * time.Second
|
||||
err = logger.Shutdown(shutdownTimeout)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Logger shutdown error: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Logger shutdown complete.")
|
||||
}
|
||||
|
||||
// NO time.Sleep needed here - log.Shutdown waits.
|
||||
fmt.Println("--- Example Finished ---")
|
||||
fmt.Printf("Check log files in './logs' and the saved config '%s'.\n", configFile)
|
||||
}
|
||||
211
example/stress/main.go
Normal file
211
example/stress/main.go
Normal file
@ -0,0 +1,211 @@
|
||||
// FILE: example/stress/main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/lixenwraith/config"
|
||||
"github.com/lixenwraith/log"
|
||||
)
|
||||
|
||||
const (
|
||||
totalBursts = 100
|
||||
logsPerBurst = 500
|
||||
maxMessageSize = 10000
|
||||
numWorkers = 500
|
||||
)
|
||||
|
||||
const configFile = "stress_config.toml"
|
||||
const configBasePath = "logstress" // Base path for log settings in config
|
||||
|
||||
// Example TOML content for stress test
|
||||
var tomlContent = `
|
||||
# Example stress_config.toml
|
||||
[logstress]
|
||||
level = -4 # Debug
|
||||
name = "stress_test"
|
||||
directory = "./logs" # Log package will create this
|
||||
format = "txt"
|
||||
extension = "log"
|
||||
show_timestamp = true
|
||||
show_level = true
|
||||
buffer_size = 500
|
||||
max_size_mb = 1 # Force frequent rotation (1MB)
|
||||
max_total_size_mb = 20 # Limit total size to force cleanup (20MB)
|
||||
min_disk_free_mb = 50
|
||||
flush_interval_ms = 50 # ms
|
||||
trace_depth = 0
|
||||
retention_period_hrs = 0.0028 # ~10 seconds
|
||||
retention_check_mins = 0.084 # ~5 seconds
|
||||
`
|
||||
|
||||
var levels = []int64{
|
||||
log.LevelDebug,
|
||||
log.LevelInfo,
|
||||
log.LevelWarn,
|
||||
log.LevelError,
|
||||
}
|
||||
|
||||
var logger *log.Logger
|
||||
|
||||
func generateRandomMessage(size int) string {
|
||||
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 "
|
||||
var sb strings.Builder
|
||||
sb.Grow(size)
|
||||
for i := 0; i < size; i++ {
|
||||
sb.WriteByte(chars[rand.Intn(len(chars))])
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// logBurst simulates a burst of logging activity
|
||||
func logBurst(burstID int) {
|
||||
for i := 0; i < logsPerBurst; i++ {
|
||||
level := levels[rand.Intn(len(levels))]
|
||||
msgSize := rand.Intn(maxMessageSize) + 10
|
||||
msg := generateRandomMessage(msgSize)
|
||||
args := []any{
|
||||
msg,
|
||||
"wkr", burstID % numWorkers,
|
||||
"bst", burstID,
|
||||
"seq", i,
|
||||
"rnd", rand.Int63(),
|
||||
}
|
||||
switch level {
|
||||
case log.LevelDebug:
|
||||
logger.Debug(args...)
|
||||
case log.LevelInfo:
|
||||
logger.Info(args...)
|
||||
case log.LevelWarn:
|
||||
logger.Warn(args...)
|
||||
case log.LevelError:
|
||||
logger.Error(args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// worker goroutine function
|
||||
func worker(burstChan chan int, wg *sync.WaitGroup, completedBursts *atomic.Int64) {
|
||||
defer wg.Done()
|
||||
for burstID := range burstChan {
|
||||
logBurst(burstID)
|
||||
completed := completedBursts.Add(1)
|
||||
if completed%10 == 0 || completed == totalBursts {
|
||||
fmt.Printf("\rProgress: %d/%d bursts completed", completed, totalBursts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UnixNano()) // Replace rand.New with rand.Seed for compatibility
|
||||
|
||||
fmt.Println("--- Logger Stress Test ---")
|
||||
|
||||
// --- Setup Config ---
|
||||
err := os.WriteFile(configFile, []byte(tomlContent), 0644)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write dummy config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Created dummy config file: %s\n", configFile)
|
||||
logsDir := "./logs" // Match config
|
||||
_ = os.RemoveAll(logsDir) // Clean previous run's LOGS directory before starting
|
||||
// defer os.Remove(configFile) // Remove to keep the saved config file
|
||||
// defer os.RemoveAll(logsDir) // Remove to keep the log directory
|
||||
|
||||
cfg := config.New()
|
||||
err = cfg.Load(configFile, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to load config: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// --- Initialize Logger ---
|
||||
logger = log.NewLogger()
|
||||
err = logger.Init(cfg, configBasePath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to initialize logger: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Logger initialized. Logs will be written to: %s\n", logsDir)
|
||||
|
||||
// --- SAVE CONFIGURATION ---
|
||||
err = cfg.Save(configFile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to save configuration to '%s': %v\n", configFile, err)
|
||||
} else {
|
||||
fmt.Printf("Configuration saved to: %s\n", configFile)
|
||||
}
|
||||
// --- End Save Configuration ---
|
||||
|
||||
fmt.Printf("Starting stress test: %d workers, %d bursts, %d logs/burst.\n",
|
||||
numWorkers, totalBursts, logsPerBurst)
|
||||
fmt.Println("Watch for 'Logs were dropped' or 'disk full' messages.")
|
||||
fmt.Println("Check log directory size and file rotation.")
|
||||
fmt.Println("Press Ctrl+C to stop early.")
|
||||
|
||||
// --- Setup Workers and Signal Handling ---
|
||||
burstChan := make(chan int, numWorkers)
|
||||
var wg sync.WaitGroup
|
||||
completedBursts := atomic.Int64{}
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
stopChan := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
<-sigChan
|
||||
fmt.Println("\n[Signal Received] Stopping burst generation...")
|
||||
close(stopChan)
|
||||
}()
|
||||
|
||||
for i := 0; i < numWorkers; i++ {
|
||||
wg.Add(1)
|
||||
go worker(burstChan, &wg, &completedBursts)
|
||||
}
|
||||
|
||||
// --- Run Test ---
|
||||
startTime := time.Now()
|
||||
for i := 1; i <= totalBursts; i++ {
|
||||
select {
|
||||
case burstChan <- i:
|
||||
case <-stopChan:
|
||||
fmt.Println("[Signal Received] Halting burst submission.")
|
||||
goto endLoop
|
||||
}
|
||||
}
|
||||
endLoop:
|
||||
close(burstChan)
|
||||
|
||||
fmt.Println("\nWaiting for workers to finish...")
|
||||
wg.Wait()
|
||||
duration := time.Since(startTime)
|
||||
finalCompleted := completedBursts.Load()
|
||||
|
||||
fmt.Printf("\n--- Test Finished ---")
|
||||
fmt.Printf("\nCompleted %d/%d bursts in %v\n", finalCompleted, totalBursts, duration.Round(time.Millisecond))
|
||||
if finalCompleted > 0 && duration.Seconds() > 0 {
|
||||
logsPerSec := float64(finalCompleted*logsPerBurst) / duration.Seconds()
|
||||
fmt.Printf("Approximate Logs/sec: %.2f\n", logsPerSec)
|
||||
}
|
||||
|
||||
// --- Shutdown Logger ---
|
||||
fmt.Println("Shutting down logger (allowing up to 10s)...")
|
||||
shutdownTimeout := 10 * time.Second
|
||||
err = logger.Shutdown(shutdownTimeout)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Logger shutdown error: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Logger shutdown complete.")
|
||||
}
|
||||
|
||||
fmt.Printf("Check log files in '%s' and the saved config '%s'.\n", logsDir, configFile)
|
||||
fmt.Println("Check stderr output above for potential errors during cleanup.")
|
||||
}
|
||||
Reference in New Issue
Block a user