mirror of
https://github.com/nabbar/golib.git
synced 2025-12-24 11:51:02 +08:00
[file/bandwidth] - ADD documentation: add enhanced README and TESTING guidelines - ADD tests: complete test suites with benchmarks, concurrency, and edge cases [file/perm] - ADD documentation: add enhanced README and TESTING guidelines - ADD tests: complete test suites with benchmarks, concurrency, and edge cases - ADD function to parse form "rwx-wxr-x" or "-rwx-w-r-x" - ADD function to ParseFileMode to convert os.FileMode to file.Perm [file/progress] - ADD documentation: add enhanced README and TESTING guidelines - ADD tests: complete test suites with benchmarks, concurrency, and edge cases [ioutils/...] - UPDATE documentation: update enhanced README and TESTING guidelines - UPDATE tests: complete test suites with benchmarks, concurrency, and edge cases [logger/...] - UPDATE documentation: update enhanced README and TESTING guidelines - ADD documentation: add enhanced README and TESTING guidelines for sub packages - UPDATE tests: complete test suites with benchmarks, concurrency, and edge cases - UPDATE config: remove FileBufferSize from OptionFile (rework hookfile) - UPDATE fields: expose Store function in interface - REWORK hookfile: rework package, use aggregator to allow multi write and single file - FIX hookstderr: fix bug with NonColorable - FIX hookstdout: fix bug with NonColorable - FIX hookwriter: fix bug with NonColorable [network/protocol] - ADD function IsTCP, IsUDP, IsUnixLike to check type of protocol [runner] - FIX typo [socket] - UPDATE documentation: update enhanced README and TESTING guidelines - ADD documentation: add enhanced README and TESTING guidelines for sub packages - UPDATE tests: complete test suites with benchmarks, concurrency, and edge cases - REWORK server: use context compatible io.reader, io.writer, io.closer instead of reader / writer - REWORK server: simplify, optimize server - REMOVE reader, writer type - ADD context: add new interface in root socket interface to expose context interface that extend context, io reader/writer/closer, dediacted function to server (IsConnected, ...)
460 lines
12 KiB
Go
460 lines
12 KiB
Go
/*
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2025 Nicolas JUHEL
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*
|
|
*
|
|
*/
|
|
|
|
package hooksyslog_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
logcfg "github.com/nabbar/golib/logger/config"
|
|
logsys "github.com/nabbar/golib/logger/hooksyslog"
|
|
libptc "github.com/nabbar/golib/network/protocol"
|
|
)
|
|
|
|
// Example_basic demonstrates the simplest use case: creating a hook that writes to local syslog.
|
|
// Note: This example uses UDP which doesn't require an actual syslog daemon to be running.
|
|
func Example_basic() {
|
|
// Configure the hook with minimal settings
|
|
// In this example, we use UDP protocol which doesn't fail if no server is running
|
|
opts := logcfg.OptionsSyslog{
|
|
Network: libptc.NetworkUDP.Code(),
|
|
Host: "localhost:514", // UDP doesn't fail without server
|
|
Tag: "myapp",
|
|
LogLevel: []string{"info", "warning", "error"},
|
|
}
|
|
|
|
// Create the hook
|
|
hook, err := logsys.New(opts, &logrus.TextFormatter{
|
|
DisableTimestamp: true, // Disable timestamp for predictable output
|
|
})
|
|
if err != nil {
|
|
fmt.Printf("Error creating hook: %v\n", err)
|
|
return
|
|
}
|
|
defer hook.Close()
|
|
|
|
// Start async writer goroutine
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
go hook.Run(ctx)
|
|
|
|
// Wait for hook to be ready
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Create and configure logger
|
|
logger := logrus.New()
|
|
logger.SetOutput(os.Stderr) // Use Stderr to separate from syslog output
|
|
logger.AddHook(hook)
|
|
|
|
// IMPORTANT: The message parameter "ignored" is NOT used by the hook in standard mode.
|
|
// Only the fields (here "msg") are written to syslog.
|
|
// Exception: In AccessLog mode, only the message is used and fields are ignored.
|
|
logger.WithField("msg", "Application started successfully").Info("ignored")
|
|
|
|
// Wait for async write
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
fmt.Println("Log sent to syslog")
|
|
|
|
// Output:
|
|
// Log sent to syslog
|
|
}
|
|
|
|
// Example_remoteUdp demonstrates sending logs to a remote syslog server via UDP.
|
|
func Example_remoteUdp() {
|
|
opts := logcfg.OptionsSyslog{
|
|
Network: libptc.NetworkUDP.Code(),
|
|
Host: "localhost:514", // Remote syslog server
|
|
Tag: "remote-app",
|
|
Facility: "LOCAL0", // Use LOCAL0 facility
|
|
LogLevel: []string{"info", "error"},
|
|
}
|
|
|
|
hook, err := logsys.New(opts, &logrus.JSONFormatter{})
|
|
if err != nil {
|
|
fmt.Printf("Error: %v\n", err)
|
|
return
|
|
}
|
|
defer hook.Close()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
go hook.Run(ctx)
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
logger := logrus.New()
|
|
logger.SetOutput(os.Stderr)
|
|
logger.AddHook(hook)
|
|
|
|
// IMPORTANT: Use fields, not message parameter
|
|
logger.WithFields(logrus.Fields{
|
|
"msg": "Remote logging test",
|
|
"service": "api",
|
|
"instance": "prod-1",
|
|
}).Info("ignored")
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
fmt.Println("Log sent to remote syslog via UDP")
|
|
|
|
// Output:
|
|
// Log sent to remote syslog via UDP
|
|
}
|
|
|
|
// Example_accessLog demonstrates using access log mode for HTTP request logging.
|
|
// In this mode, behavior is reversed: the message IS written, fields are IGNORED.
|
|
func Example_accessLog() {
|
|
opts := logcfg.OptionsSyslog{
|
|
Network: libptc.NetworkUDP.Code(),
|
|
Host: "localhost:514",
|
|
Tag: "http-access",
|
|
EnableAccessLog: true, // Message-only mode
|
|
LogLevel: []string{"info"},
|
|
}
|
|
|
|
hook, err := logsys.New(opts, nil)
|
|
if err != nil {
|
|
fmt.Printf("Error: %v\n", err)
|
|
return
|
|
}
|
|
defer hook.Close()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
go hook.Run(ctx)
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
logger := logrus.New()
|
|
logger.SetOutput(os.Stderr)
|
|
logger.AddHook(hook)
|
|
|
|
// IMPORTANT: In AccessLog mode, behavior is REVERSED!
|
|
// The message "GET /api/users - 200 OK - 45ms" IS output.
|
|
// The fields (method, path, status) are IGNORED.
|
|
logger.WithFields(logrus.Fields{
|
|
"method": "GET",
|
|
"path": "/api/users",
|
|
"status": 200,
|
|
}).Info("GET /api/users - 200 OK - 45ms")
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
fmt.Println("Access log sent to syslog")
|
|
|
|
// Output:
|
|
// Access log sent to syslog
|
|
}
|
|
|
|
// Example_levelFiltering demonstrates filtering logs by level.
|
|
func Example_levelFiltering() {
|
|
opts := logcfg.OptionsSyslog{
|
|
Network: libptc.NetworkUDP.Code(),
|
|
Host: "localhost:514",
|
|
Tag: "filtered-app",
|
|
LogLevel: []string{"error", "fatal"}, // Only errors and above
|
|
}
|
|
|
|
hook, err := logsys.New(opts, &logrus.TextFormatter{
|
|
DisableTimestamp: true,
|
|
})
|
|
if err != nil {
|
|
fmt.Printf("Error: %v\n", err)
|
|
return
|
|
}
|
|
defer hook.Close()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
go hook.Run(ctx)
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
logger := logrus.New()
|
|
logger.SetOutput(os.Stderr)
|
|
logger.AddHook(hook)
|
|
|
|
// This will be written (error level)
|
|
// Note: message "ignored" is NOT used, only the field "msg"
|
|
logger.WithField("msg", "Database connection failed").Error("ignored")
|
|
|
|
// This won't be written to syslog (wrong level)
|
|
logger.WithField("msg", "Request completed").Info("ignored")
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
fmt.Println("Filtered logs sent to syslog")
|
|
|
|
// Output:
|
|
// Filtered logs sent to syslog
|
|
}
|
|
|
|
// Example_fieldFiltering demonstrates filtering specific fields from output.
|
|
func Example_fieldFiltering() {
|
|
// Configure to filter out stack and timestamp
|
|
opts := logcfg.OptionsSyslog{
|
|
Network: libptc.NetworkUDP.Code(),
|
|
Host: "localhost:514",
|
|
Tag: "clean-app",
|
|
DisableStack: true, // Remove stack fields
|
|
DisableTimestamp: true, // Remove time fields
|
|
EnableTrace: false, // Remove caller/file/line fields
|
|
LogLevel: []string{"info"},
|
|
}
|
|
|
|
hook, err := logsys.New(opts, &logrus.TextFormatter{
|
|
DisableTimestamp: true,
|
|
})
|
|
if err != nil {
|
|
fmt.Printf("Error: %v\n", err)
|
|
return
|
|
}
|
|
defer hook.Close()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
go hook.Run(ctx)
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
logger := logrus.New()
|
|
logger.SetOutput(os.Stderr)
|
|
logger.AddHook(hook)
|
|
|
|
// IMPORTANT: message "ignored" is NOT used, only fields
|
|
logger.WithFields(logrus.Fields{
|
|
"msg": "Filtered log entry",
|
|
"stack": "will be filtered out",
|
|
"caller": "will be filtered out",
|
|
"user": "john",
|
|
"action": "login",
|
|
}).Info("ignored")
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
fmt.Println("Filtered log sent to syslog")
|
|
|
|
// Output:
|
|
// Filtered log sent to syslog
|
|
}
|
|
|
|
// Example_gracefulShutdown demonstrates proper shutdown with the Done channel.
|
|
func Example_gracefulShutdown() {
|
|
opts := logcfg.OptionsSyslog{
|
|
Network: libptc.NetworkUDP.Code(),
|
|
Host: "localhost:514",
|
|
Tag: "shutdown-test",
|
|
LogLevel: []string{"info"},
|
|
}
|
|
|
|
hook, err := logsys.New(opts, nil)
|
|
if err != nil {
|
|
fmt.Printf("Error: %v\n", err)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
go hook.Run(ctx)
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
logger := logrus.New()
|
|
logger.SetOutput(os.Stderr)
|
|
logger.AddHook(hook)
|
|
|
|
// Send some logs
|
|
logger.WithField("msg", "Starting shutdown sequence").Info("ignored")
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Signal shutdown
|
|
cancel()
|
|
hook.Close()
|
|
|
|
// Wait for completion
|
|
select {
|
|
case <-hook.Done():
|
|
fmt.Println("Hook shutdown complete")
|
|
case <-time.After(2 * time.Second):
|
|
fmt.Println("Timeout waiting for shutdown")
|
|
}
|
|
|
|
// Output:
|
|
// Hook shutdown complete
|
|
}
|
|
|
|
// Example_structuredLogging demonstrates structured logging with JSON formatter.
|
|
func Example_structuredLogging() {
|
|
opts := logcfg.OptionsSyslog{
|
|
Network: libptc.NetworkUDP.Code(),
|
|
Host: "localhost:514",
|
|
Tag: "structured-app",
|
|
LogLevel: []string{"info"},
|
|
}
|
|
|
|
hook, err := logsys.New(opts, &logrus.JSONFormatter{})
|
|
if err != nil {
|
|
fmt.Printf("Error: %v\n", err)
|
|
return
|
|
}
|
|
defer hook.Close()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
go hook.Run(ctx)
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
logger := logrus.New()
|
|
logger.SetOutput(os.Stderr)
|
|
logger.AddHook(hook)
|
|
|
|
// IMPORTANT: message parameter is NOT used, only fields
|
|
logger.WithFields(logrus.Fields{
|
|
"user_id": 12345,
|
|
"action": "purchase",
|
|
"amount": 99.99,
|
|
"currency": "USD",
|
|
"msg": "Purchase completed",
|
|
"request_id": "abc-123-def",
|
|
}).Info("ignored")
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
fmt.Println("Structured log sent to syslog")
|
|
|
|
// Output:
|
|
// Structured log sent to syslog
|
|
}
|
|
|
|
// Example_multipleHooks demonstrates using multiple hooks for different destinations.
|
|
func Example_multipleHooks() {
|
|
// Hook for errors only
|
|
errorOpts := logcfg.OptionsSyslog{
|
|
Network: libptc.NetworkUDP.Code(),
|
|
Host: "localhost:514",
|
|
Tag: "errors",
|
|
LogLevel: []string{"error", "fatal"},
|
|
}
|
|
|
|
errorHook, err := logsys.New(errorOpts, nil)
|
|
if err != nil {
|
|
fmt.Printf("Error: %v\n", err)
|
|
return
|
|
}
|
|
defer errorHook.Close()
|
|
|
|
// Hook for all levels
|
|
allOpts := logcfg.OptionsSyslog{
|
|
Network: libptc.NetworkUDP.Code(),
|
|
Host: "localhost:514",
|
|
Tag: "all-logs",
|
|
}
|
|
|
|
allHook, err := logsys.New(allOpts, nil)
|
|
if err != nil {
|
|
fmt.Printf("Error: %v\n", err)
|
|
return
|
|
}
|
|
defer allHook.Close()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
go errorHook.Run(ctx)
|
|
go allHook.Run(ctx)
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
logger := logrus.New()
|
|
logger.SetOutput(os.Stderr)
|
|
logger.AddHook(errorHook)
|
|
logger.AddHook(allHook)
|
|
|
|
// This goes to both hooks
|
|
logger.WithField("msg", "Critical error occurred").Error("ignored")
|
|
|
|
// This goes only to allHook
|
|
logger.WithField("msg", "Normal operation").Info("ignored")
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
fmt.Println("Logs sent to multiple syslog destinations")
|
|
|
|
// Output:
|
|
// Logs sent to multiple syslog destinations
|
|
}
|
|
|
|
// Example_traceEnabled demonstrates enabling trace information in logs.
|
|
func Example_traceEnabled() {
|
|
opts := logcfg.OptionsSyslog{
|
|
Network: libptc.NetworkUDP.Code(),
|
|
Host: "localhost:514",
|
|
Tag: "trace-app",
|
|
EnableTrace: true, // Include caller/file/line information
|
|
LogLevel: []string{"info"},
|
|
}
|
|
|
|
hook, err := logsys.New(opts, &logrus.TextFormatter{
|
|
DisableTimestamp: true,
|
|
})
|
|
if err != nil {
|
|
fmt.Printf("Error: %v\n", err)
|
|
return
|
|
}
|
|
defer hook.Close()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
go hook.Run(ctx)
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
logger := logrus.New()
|
|
logger.SetOutput(os.Stderr)
|
|
logger.AddHook(hook)
|
|
|
|
// IMPORTANT: message "ignored" is NOT used, only fields
|
|
logger.WithFields(logrus.Fields{
|
|
"msg": "Log with trace info",
|
|
"caller": "main.processRequest",
|
|
"file": "main.go",
|
|
"line": 42,
|
|
}).Info("ignored")
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
fmt.Println("Log with trace sent to syslog")
|
|
|
|
// Output:
|
|
// Log with trace sent to syslog
|
|
}
|