Merge pull request 'bbenouarets-20251110' (#2) from bbenouarets-20251110 into main
Reviewed-on: #2
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.log
|
||||
example/app.log
|
||||
129
README.md
129
README.md
@@ -22,6 +22,8 @@ go get git.secnex.io/secnex/masterlog
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
@@ -30,7 +32,7 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Simple logging
|
||||
// Simple logging using the global logger
|
||||
masterlog.Info("Hello, World!")
|
||||
|
||||
// Logging with fields
|
||||
@@ -47,6 +49,45 @@ func main() {
|
||||
2025-11-10T05:06:02+01:00 INF main.go:12 > User logged in user_id=12345 ip=192.168.1.1 go_version=go1.25.3 pid=12345
|
||||
```
|
||||
|
||||
### Configuring the Global Logger
|
||||
|
||||
Configure the global logger once at application startup:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.secnex.io/secnex/masterlog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure global logger at startup
|
||||
masterlog.SetLevel(masterlog.LevelDebug)
|
||||
|
||||
// Add file writer
|
||||
fileWriter, _ := masterlog.NewFileWriter("app.log")
|
||||
defer fileWriter.Close()
|
||||
masterlog.AddWriter(fileWriter)
|
||||
|
||||
// Add JSON encoder
|
||||
masterlog.AddEncoder(&masterlog.JSONEncoder{})
|
||||
|
||||
// Configure pseudonymization
|
||||
pseudonymizer := masterlog.NewPseudonymizerFromEnv("LOG_SECRET")
|
||||
masterlog.SetPseudonymizer(pseudonymizer)
|
||||
masterlog.AddSensitiveFields("user_id", "email", "ip")
|
||||
|
||||
// Now all package-level functions use the configured global logger
|
||||
masterlog.Info("Application started")
|
||||
masterlog.Debug("Debug message")
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Configure once, use everywhere
|
||||
- No need to pass logger instances around
|
||||
- Simple and convenient for most use cases
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Log Levels](#log-levels)
|
||||
@@ -211,6 +252,22 @@ logger.Info("Console log")
|
||||
|
||||
Writes log entries to a file.
|
||||
|
||||
**Using with global logger (recommended):**
|
||||
|
||||
```go
|
||||
// Add file writer to global logger
|
||||
fileWriter, err := masterlog.NewFileWriter("app.log")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer fileWriter.Close()
|
||||
|
||||
masterlog.AddWriter(fileWriter)
|
||||
masterlog.Info("File log") // Uses global logger
|
||||
```
|
||||
|
||||
**Using with custom logger:**
|
||||
|
||||
```go
|
||||
logger := masterlog.New()
|
||||
fileWriter, err := masterlog.NewFileWriter("app.log")
|
||||
@@ -239,9 +296,42 @@ logger.AddWriter(fileWriter)
|
||||
logger.Info("Multi-destination log")
|
||||
```
|
||||
|
||||
## Custom Logger Instances
|
||||
## Global Logger vs Custom Instances
|
||||
|
||||
Create custom logger instances with specific configurations:
|
||||
MasterLog provides two ways to use logging:
|
||||
|
||||
### Global Logger (Recommended for Most Cases)
|
||||
|
||||
The global logger is pre-configured and ready to use. Configure it once at startup:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// Configure global logger at startup
|
||||
masterlog.SetLevel(masterlog.LevelDebug)
|
||||
|
||||
// Add file writer
|
||||
fileWriter, _ := masterlog.NewFileWriter("app.log")
|
||||
defer fileWriter.Close()
|
||||
masterlog.AddWriter(fileWriter)
|
||||
|
||||
// Add JSON encoder
|
||||
masterlog.AddEncoder(&masterlog.JSONEncoder{})
|
||||
|
||||
// Use package-level functions anywhere in your code
|
||||
masterlog.Info("This uses the global logger")
|
||||
masterlog.Debug("No need to pass logger instances around")
|
||||
}
|
||||
```
|
||||
|
||||
**Advantages:**
|
||||
- Simple and convenient
|
||||
- No need to pass logger instances
|
||||
- Configure once, use everywhere
|
||||
- Perfect for most applications
|
||||
|
||||
### Custom Logger Instances
|
||||
|
||||
Create custom logger instances when you need different configurations for different components:
|
||||
|
||||
```go
|
||||
// Create logger with custom level
|
||||
@@ -258,6 +348,37 @@ logger.AddWriter(fileWriter)
|
||||
logger.Info("Custom logger example")
|
||||
```
|
||||
|
||||
**Use Cases:**
|
||||
- Different log levels for different components
|
||||
- Separate log files for different modules
|
||||
- Different encoders for different outputs
|
||||
- Component-specific pseudonymization rules
|
||||
|
||||
### Replacing the Global Logger
|
||||
|
||||
You can also replace the global logger with a custom instance:
|
||||
|
||||
```go
|
||||
// Create and configure a custom logger
|
||||
customLogger := masterlog.New(masterlog.LevelTrace)
|
||||
customLogger.AddEncoder(&masterlog.JSONEncoder{})
|
||||
|
||||
// Replace the global logger
|
||||
masterlog.SetDefaultLogger(customLogger)
|
||||
|
||||
// Now all package-level functions use your custom logger
|
||||
masterlog.Info("Uses the custom logger")
|
||||
```
|
||||
|
||||
### Getting the Global Logger
|
||||
|
||||
Access the global logger instance directly if needed:
|
||||
|
||||
```go
|
||||
globalLogger := masterlog.GetDefaultLogger()
|
||||
globalLogger.AddWriter(fileWriter)
|
||||
```
|
||||
|
||||
## Data Pseudonymization
|
||||
|
||||
MasterLog includes built-in support for deterministic pseudonymization of sensitive data using HMAC-SHA256. This allows you to:
|
||||
@@ -415,6 +536,8 @@ func AddEncoder(encoder Encoder)
|
||||
func SetPseudonymizer(pseudonymizer *Pseudonymizer)
|
||||
func AddSensitiveField(fieldName string)
|
||||
func AddSensitiveFields(fieldNames ...string)
|
||||
func SetDefaultLogger(logger *MasterLogger)
|
||||
func GetDefaultLogger() *MasterLogger
|
||||
```
|
||||
|
||||
### Logger Methods
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
2025-11-10T05:00:34+01:00 INF proc.go:285 > This will be written to file go_version=go1.25.3 pid=71020
|
||||
2025-11-10T05:01:36+01:00 INF proc.go:285 > This will be written to file go_version=go1.25.3 pid=71657
|
||||
2025-11-10T05:01:59+01:00 INF proc.go:285 > This will be written to file go_version=go1.25.3 pid=72132
|
||||
2025-11-10T05:02:14+01:00 INF proc.go:285 > This will be written to file go_version=go1.25.3 pid=72401
|
||||
2025-11-10T05:05:14+01:00 INF main.go:41 > This will be written to file go_version=go1.25.3 pid=73856
|
||||
2025-11-10T05:06:02+01:00 INF main.go:41 > This will be written to file go_version=go1.25.3 pid=74313
|
||||
2025-11-10T05:07:19+01:00 INF main.go:41 > This will be written to file go_version=go1.25.3 pid=75031
|
||||
2025-11-10T05:09:48+01:00 INF main.go:41 > This will be written to file go_version=go1.25.3 pid=76511
|
||||
2025-11-10T05:15:16+01:00 INF main.go:41 > This will be written to file go_version=go1.25.3 pid=79210
|
||||
2025-11-10T05:15:26+01:00 INF main.go:41 > This will be written to file go_version=go1.25.3 pid=79455
|
||||
@@ -5,7 +5,22 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Example 0: Configure global logger once at startup
|
||||
// Add file writer to global logger
|
||||
fileWriter, err := masterlog.NewFileWriter("app.log")
|
||||
if err == nil {
|
||||
masterlog.AddWriter(fileWriter)
|
||||
defer fileWriter.Close()
|
||||
}
|
||||
|
||||
// Add JSON encoder to global logger
|
||||
masterlog.AddEncoder(&masterlog.JSONEncoder{})
|
||||
|
||||
// Set log level for global logger
|
||||
masterlog.SetLevel(masterlog.LevelDebug)
|
||||
|
||||
// Example 1: Simple formatted logging to console (with colors if terminal)
|
||||
// Now uses the configured global logger (writes to console, file, and JSON)
|
||||
masterlog.Info("Hello, World!")
|
||||
|
||||
// Example 2: Logging with fields
|
||||
@@ -25,21 +40,22 @@ func main() {
|
||||
traceLogger := masterlog.New(masterlog.LevelTrace)
|
||||
traceLogger.Info("Logger created with LevelTrace")
|
||||
|
||||
// Example 5: Create custom logger with JSON encoder
|
||||
// Example 5: JSON encoder is already added to global logger in Example 0
|
||||
// Here's how you'd use it with a custom logger if needed:
|
||||
jsonLogger := masterlog.New()
|
||||
jsonLogger.AddEncoder(&masterlog.JSONEncoder{})
|
||||
jsonLogger.Info("JSON formatted log", map[string]interface{}{
|
||||
jsonLogger.Info("JSON formatted log (custom logger)", map[string]interface{}{
|
||||
"key": "value",
|
||||
})
|
||||
|
||||
// Example 6: Create logger with file writer
|
||||
fileLogger := masterlog.New()
|
||||
fileWriter, err := masterlog.NewFileWriter("app.log")
|
||||
if err == nil {
|
||||
fileLogger.AddWriter(fileWriter)
|
||||
defer fileWriter.Close()
|
||||
fileLogger.Info("This will be written to file")
|
||||
}
|
||||
// Example 6: Add file writer to global logger (already configured above)
|
||||
// Note: The fileWriter was already added in Example 0, but here's how you'd do it separately
|
||||
// fileWriter, err := masterlog.NewFileWriter("app.log")
|
||||
// if err == nil {
|
||||
// masterlog.AddWriter(fileWriter)
|
||||
// defer fileWriter.Close()
|
||||
// }
|
||||
masterlog.Info("This will be written to console, file, and JSON (all configured globally)")
|
||||
|
||||
// Example 7: Logger with multiple encoders (formatted + JSON)
|
||||
multiLogger := masterlog.New()
|
||||
@@ -58,17 +74,16 @@ func main() {
|
||||
debugLogger.Debug("Debug logger with colors")
|
||||
debugLogger.Trace("This won't be logged")
|
||||
|
||||
// Example 10: Pseudonymization of sensitive data
|
||||
pseudoLogger := masterlog.New()
|
||||
// Example 10: Pseudonymization of sensitive data using global logger
|
||||
// Create a pseudonymizer with a secret (in production, use a secure secret from config/env)
|
||||
pseudonymizer := masterlog.NewPseudonymizerFromString("my-secret-key-change-in-production")
|
||||
pseudoLogger.SetPseudonymizer(pseudonymizer)
|
||||
masterlog.SetPseudonymizer(pseudonymizer)
|
||||
|
||||
// Mark fields as sensitive
|
||||
pseudoLogger.AddSensitiveFields("user_id", "email", "ip")
|
||||
// Mark fields as sensitive (on global logger)
|
||||
masterlog.AddSensitiveFields("user_id", "email", "ip")
|
||||
|
||||
// Log with sensitive data - it will be pseudonymized
|
||||
pseudoLogger.Info("User logged in", map[string]interface{}{
|
||||
// Log with sensitive data - it will be pseudonymized (using global logger)
|
||||
masterlog.Info("User logged in", map[string]interface{}{
|
||||
"user_id": 12345,
|
||||
"email": "user@example.com",
|
||||
"ip": "192.168.1.1",
|
||||
@@ -76,7 +91,7 @@ func main() {
|
||||
})
|
||||
|
||||
// Same user_id will produce the same pseudonymized value (deterministic)
|
||||
pseudoLogger.Info("User performed action", map[string]interface{}{
|
||||
masterlog.Info("User performed action", map[string]interface{}{
|
||||
"user_id": 12345, // Same value, same pseudonymized output
|
||||
"action": "purchase",
|
||||
})
|
||||
|
||||
30
logger.go
30
logger.go
@@ -135,6 +135,24 @@ func (l *MasterLogger) log(level Level, message string, file string, line int, f
|
||||
}
|
||||
}
|
||||
|
||||
// Skip FormattedEncoder for file writers if JSONEncoder is present
|
||||
// This ensures files only get JSON format when JSONEncoder is added
|
||||
if _, isFormatted := encoder.(*FormattedEncoder); isFormatted {
|
||||
if _, isFile := writer.(*FileWriter); isFile {
|
||||
// Check if JSONEncoder exists in encoders
|
||||
hasJSONEncoder := false
|
||||
for _, e := range l.encoders {
|
||||
if _, ok := e.(*JSONEncoder); ok {
|
||||
hasJSONEncoder = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasJSONEncoder {
|
||||
continue // Don't write formatted to file if JSON encoder exists
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
@@ -185,6 +203,18 @@ func (l *MasterLogger) Error(message string, fields ...map[string]interface{}) {
|
||||
// Default logger instance
|
||||
var defaultLogger = New(LevelInfo)
|
||||
|
||||
// SetDefaultLogger replaces the default logger instance
|
||||
// This allows you to configure a custom logger and use it globally
|
||||
func SetDefaultLogger(logger *MasterLogger) {
|
||||
defaultLogger = logger
|
||||
}
|
||||
|
||||
// GetDefaultLogger returns the current default logger instance
|
||||
// Useful if you want to configure it directly
|
||||
func GetDefaultLogger() *MasterLogger {
|
||||
return defaultLogger
|
||||
}
|
||||
|
||||
// Package-level convenience functions
|
||||
// These collect caller info before calling the instance method
|
||||
func Trace(message string, fields ...map[string]interface{}) {
|
||||
|
||||
Reference in New Issue
Block a user