feat(log): Formatted console logging with file output

This commit is contained in:
Björn Benouarets
2025-11-10 05:25:29 +01:00
parent 7b3c176d20
commit 764945c07e
3 changed files with 171 additions and 21 deletions

129
README.md
View File

@@ -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

View File

@@ -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",
}) })

View File

@@ -185,6 +185,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{}) {