Merge pull request 'bbenouarets-20251110' (#2) from bbenouarets-20251110 into main
Reviewed-on: #2
This commit was merged in pull request #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
|
## Quick Start
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Simple logging
|
// Simple logging using the global logger
|
||||||
masterlog.Info("Hello, World!")
|
masterlog.Info("Hello, World!")
|
||||||
|
|
||||||
// Logging with fields
|
// 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
|
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
|
## Table of Contents
|
||||||
|
|
||||||
- [Log Levels](#log-levels)
|
- [Log Levels](#log-levels)
|
||||||
@@ -211,6 +252,22 @@ logger.Info("Console log")
|
|||||||
|
|
||||||
Writes log entries to a file.
|
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
|
```go
|
||||||
logger := masterlog.New()
|
logger := masterlog.New()
|
||||||
fileWriter, err := masterlog.NewFileWriter("app.log")
|
fileWriter, err := masterlog.NewFileWriter("app.log")
|
||||||
@@ -239,9 +296,42 @@ logger.AddWriter(fileWriter)
|
|||||||
logger.Info("Multi-destination log")
|
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
|
```go
|
||||||
// Create logger with custom level
|
// Create logger with custom level
|
||||||
@@ -258,6 +348,37 @@ logger.AddWriter(fileWriter)
|
|||||||
logger.Info("Custom logger example")
|
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
|
## Data Pseudonymization
|
||||||
|
|
||||||
MasterLog includes built-in support for deterministic pseudonymization of sensitive data using HMAC-SHA256. This allows you to:
|
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 SetPseudonymizer(pseudonymizer *Pseudonymizer)
|
||||||
func AddSensitiveField(fieldName string)
|
func AddSensitiveField(fieldName string)
|
||||||
func AddSensitiveFields(fieldNames ...string)
|
func AddSensitiveFields(fieldNames ...string)
|
||||||
|
func SetDefaultLogger(logger *MasterLogger)
|
||||||
|
func GetDefaultLogger() *MasterLogger
|
||||||
```
|
```
|
||||||
|
|
||||||
### Logger Methods
|
### 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() {
|
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)
|
// 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!")
|
masterlog.Info("Hello, World!")
|
||||||
|
|
||||||
// Example 2: Logging with fields
|
// Example 2: Logging with fields
|
||||||
@@ -25,21 +40,22 @@ func main() {
|
|||||||
traceLogger := masterlog.New(masterlog.LevelTrace)
|
traceLogger := masterlog.New(masterlog.LevelTrace)
|
||||||
traceLogger.Info("Logger created with 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 := masterlog.New()
|
||||||
jsonLogger.AddEncoder(&masterlog.JSONEncoder{})
|
jsonLogger.AddEncoder(&masterlog.JSONEncoder{})
|
||||||
jsonLogger.Info("JSON formatted log", map[string]interface{}{
|
jsonLogger.Info("JSON formatted log (custom logger)", map[string]interface{}{
|
||||||
"key": "value",
|
"key": "value",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Example 6: Create logger with file writer
|
// Example 6: Add file writer to global logger (already configured above)
|
||||||
fileLogger := masterlog.New()
|
// Note: The fileWriter was already added in Example 0, but here's how you'd do it separately
|
||||||
fileWriter, err := masterlog.NewFileWriter("app.log")
|
// fileWriter, err := masterlog.NewFileWriter("app.log")
|
||||||
if err == nil {
|
// if err == nil {
|
||||||
fileLogger.AddWriter(fileWriter)
|
// masterlog.AddWriter(fileWriter)
|
||||||
defer fileWriter.Close()
|
// defer fileWriter.Close()
|
||||||
fileLogger.Info("This will be written to file")
|
// }
|
||||||
}
|
masterlog.Info("This will be written to console, file, and JSON (all configured globally)")
|
||||||
|
|
||||||
// Example 7: Logger with multiple encoders (formatted + JSON)
|
// Example 7: Logger with multiple encoders (formatted + JSON)
|
||||||
multiLogger := masterlog.New()
|
multiLogger := masterlog.New()
|
||||||
@@ -58,17 +74,16 @@ func main() {
|
|||||||
debugLogger.Debug("Debug logger with colors")
|
debugLogger.Debug("Debug logger with colors")
|
||||||
debugLogger.Trace("This won't be logged")
|
debugLogger.Trace("This won't be logged")
|
||||||
|
|
||||||
// Example 10: Pseudonymization of sensitive data
|
// Example 10: Pseudonymization of sensitive data using global logger
|
||||||
pseudoLogger := masterlog.New()
|
|
||||||
// Create a pseudonymizer with a secret (in production, use a secure secret from config/env)
|
// Create a pseudonymizer with a secret (in production, use a secure secret from config/env)
|
||||||
pseudonymizer := masterlog.NewPseudonymizerFromString("my-secret-key-change-in-production")
|
pseudonymizer := masterlog.NewPseudonymizerFromString("my-secret-key-change-in-production")
|
||||||
pseudoLogger.SetPseudonymizer(pseudonymizer)
|
masterlog.SetPseudonymizer(pseudonymizer)
|
||||||
|
|
||||||
// Mark fields as sensitive
|
// Mark fields as sensitive (on global logger)
|
||||||
pseudoLogger.AddSensitiveFields("user_id", "email", "ip")
|
masterlog.AddSensitiveFields("user_id", "email", "ip")
|
||||||
|
|
||||||
// Log with sensitive data - it will be pseudonymized
|
// Log with sensitive data - it will be pseudonymized (using global logger)
|
||||||
pseudoLogger.Info("User logged in", map[string]interface{}{
|
masterlog.Info("User logged in", map[string]interface{}{
|
||||||
"user_id": 12345,
|
"user_id": 12345,
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"ip": "192.168.1.1",
|
"ip": "192.168.1.1",
|
||||||
@@ -76,7 +91,7 @@ func main() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Same user_id will produce the same pseudonymized value (deterministic)
|
// 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
|
"user_id": 12345, // Same value, same pseudonymized output
|
||||||
"action": "purchase",
|
"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 data []byte
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -185,6 +203,18 @@ func (l *MasterLogger) Error(message string, fields ...map[string]interface{}) {
|
|||||||
// Default logger instance
|
// Default logger instance
|
||||||
var defaultLogger = New(LevelInfo)
|
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
|
// Package-level convenience functions
|
||||||
// These collect caller info before calling the instance method
|
// These collect caller info before calling the instance method
|
||||||
func Trace(message string, fields ...map[string]interface{}) {
|
func Trace(message string, fields ...map[string]interface{}) {
|
||||||
|
|||||||
Reference in New Issue
Block a user