-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(configureable-logger): add support of configurable logger. (#81)
Co-authored-by: Muhammad Haseeb <muhammmadhaseebisb@gmail.com>
- Loading branch information
1 parent
23fbdad
commit 602e640
Showing
11 changed files
with
911 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package logger | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
) | ||
|
||
// LoggerInterface represents an interface for a generic logger. | ||
type LoggerInterface interface { | ||
// Log function provides a message string containing placeholders in the format '%{key}', | ||
// along with the log level and a map of parameters that can be replaced in the message. | ||
Log(level Level, message string, params map[string]any) | ||
} | ||
|
||
// ConsoleLogger represents a logger implementation that logs messages to the console. | ||
type ConsoleLogger struct{} | ||
|
||
// Log function provides a message string containing placeholders in the format '%{key}', | ||
// along with the log level and a map of parameters that can be replaced in the message. | ||
func (c ConsoleLogger) Log(level Level, message string, params map[string]any) { | ||
fmt.Println(level, ": ", _formatMessage(message, params)) | ||
} | ||
|
||
func _formatMessage(msg string, obj map[string]interface{}) string { | ||
regex := regexp.MustCompile(`\%{([^}]+)}`) | ||
|
||
formattedMsg := regex.ReplaceAllStringFunc(msg, func(match string) string { | ||
key := match[2 : len(match)-1] | ||
if value, ok := obj[key]; ok { | ||
switch v := value.(type) { | ||
case string: | ||
return v | ||
default: | ||
return fmt.Sprintf("%v", v) | ||
} | ||
} | ||
return match | ||
}) | ||
|
||
return formattedMsg | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package logger | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
// Level is a string enum. | ||
// An enum representing different log levels. | ||
type Level string | ||
|
||
// MarshalJSON implements the json.Marshaller interface for Level. | ||
// It customizes the JSON marshaling process for Level objects. | ||
func (e Level) MarshalJSON() ( | ||
[]byte, | ||
error) { | ||
if e.isValid() { | ||
return []byte(fmt.Sprintf("\"%v\"", e)), nil | ||
} | ||
return nil, errors.New("the provided enum value is not allowed for Level") | ||
} | ||
|
||
// UnmarshalJSON implements the json.Unmarshaler interface for Level. | ||
// It customizes the JSON unmarshalling process for Level objects. | ||
func (e *Level) UnmarshalJSON(input []byte) error { | ||
var enumValue string | ||
err := json.Unmarshal(input, &enumValue) | ||
if err != nil { | ||
return err | ||
} | ||
*e = Level(enumValue) | ||
if !e.isValid() { | ||
return errors.New("the value " + string(input) + " cannot be unmarshalled to Level") | ||
} | ||
return nil | ||
} | ||
|
||
// Checks whether the value is actually a member of Level. | ||
func (e *Level) isValid() bool { | ||
switch *e { | ||
case Level_ERROR, | ||
Level_WARN, | ||
Level_INFO, | ||
Level_DEBUG, | ||
Level_TRACE: | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
const ( | ||
Level_ERROR Level = "error" // Error log level. | ||
Level_WARN Level = "warn" // Warning log level. | ||
Level_INFO Level = "info" // Information log level. | ||
Level_DEBUG Level = "debug" // Debug log level. | ||
Level_TRACE Level = "trace" // Trace log level. | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package logger | ||
|
||
import ( | ||
"encoding/json" | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func validateLevelEnumValues(level Level, t *testing.T) { | ||
bytes, err := json.Marshal(level) | ||
if err != nil { | ||
t.Errorf("Unable to marshal level type : %v", err) | ||
} | ||
var newLevel Level | ||
err = json.Unmarshal(bytes, &newLevel) | ||
if err != nil { | ||
t.Errorf("Unable to unmarshal bytes into level type : %v", err) | ||
} | ||
|
||
if !reflect.DeepEqual(level, newLevel) { | ||
t.Errorf("Failed:\nExpected: %v\nGot: %v", level, newLevel) | ||
} | ||
} | ||
|
||
func TestLevelEnumValueERROR(t *testing.T) { | ||
level := Level(Level_ERROR) | ||
validateLevelEnumValues(level, t) | ||
} | ||
|
||
func TestLevelEnumValueWARN(t *testing.T) { | ||
level := Level(Level_WARN) | ||
validateLevelEnumValues(level, t) | ||
} | ||
|
||
func TestLevelEnumValueINFO(t *testing.T) { | ||
level := Level(Level_INFO) | ||
validateLevelEnumValues(level, t) | ||
} | ||
func TestLevelEnumValueDEBUG(t *testing.T) { | ||
level := Level(Level_DEBUG) | ||
validateLevelEnumValues(level, t) | ||
} | ||
|
||
func TestLevelEnumValueTRACE(t *testing.T) { | ||
level := Level(Level_TRACE) | ||
validateLevelEnumValues(level, t) | ||
} | ||
|
||
func TestLevelEnumValueInvalid(t *testing.T) { | ||
level := Level("Invalid") | ||
validateLevelEnumValues(level, new(testing.T)) | ||
} | ||
|
||
func TestLevelEnumValueInvalid2(t *testing.T) { | ||
level := Level("nil") | ||
validateLevelEnumValues(level, new(testing.T)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package logger | ||
|
||
// LoggerConfiguration represents options for configuring logging behavior. | ||
type LoggerConfiguration struct { | ||
// The logger to use for logging messages. | ||
logger LoggerInterface | ||
// The log level to determine which messages should be logged. | ||
level Level | ||
// Options for logging HTTP requests. | ||
request RequestLoggerConfiguration | ||
// Options for logging HTTP responses. | ||
response ResponseLoggerConfiguration | ||
// Indicates whether sensitive headers should be masked in logged messages. | ||
maskSensitiveHeaders bool | ||
} | ||
|
||
func (l *LoggerConfiguration) isValid() bool { | ||
return l.logger != nil && l.level.isValid() | ||
} | ||
|
||
// LoggerOptions represents a function type that can be used to apply configuration to the LoggerOptions struct. | ||
type LoggerOptions func(*LoggerConfiguration) | ||
|
||
// Default logger configuration | ||
func defaultLoggerConfiguration() LoggerConfiguration { | ||
return LoggerConfiguration{ | ||
logger: ConsoleLogger{}, | ||
level: Level_INFO, | ||
request: NewHttpRequestLoggerConfiguration(), | ||
response: NewResponseLoggerConfiguration(), | ||
maskSensitiveHeaders: true, | ||
} | ||
} | ||
|
||
// NewLoggerConfiguration creates default LoggingOptions with the provided options. | ||
func NewLoggerConfiguration(options ...LoggerOptions) LoggerConfiguration { | ||
config := defaultLoggerConfiguration() | ||
|
||
for _, option := range options { | ||
option(&config) | ||
} | ||
return config | ||
} | ||
|
||
// WithLogger is an option that sets the LoggerInterface in the LoggingOptions. | ||
func WithLogger(logger LoggerInterface) LoggerOptions { | ||
return func(l *LoggerConfiguration) { | ||
l.logger = logger | ||
} | ||
} | ||
|
||
// WithLevel is an option that sets the LogLevel in the LoggingOptions. | ||
func WithLevel(level Level) LoggerOptions { | ||
return func(l *LoggerConfiguration) { | ||
l.level = level | ||
} | ||
} | ||
|
||
// WithMaskSensitiveHeaders is an option that enable to mask Sensitive Headers in the LoggingOptions. | ||
func WithMaskSensitiveHeaders(maskSensitiveHeaders bool) LoggerOptions { | ||
return func(l *LoggerConfiguration) { | ||
l.maskSensitiveHeaders = maskSensitiveHeaders | ||
} | ||
} | ||
|
||
// WithRequestConfiguration is an option that sets that enable to log Request in the LoggingOptions. | ||
func WithRequestConfiguration(options ...RequestLoggerOptions) LoggerOptions { | ||
return func(l *LoggerConfiguration) { | ||
l.request = NewHttpRequestLoggerConfiguration(options...) | ||
} | ||
} | ||
|
||
// WithResponseConfiguration is an option that sets that enable to log Response in the LoggingOptions. | ||
func WithResponseConfiguration(options ...ResponseLoggerOptions) LoggerOptions { | ||
return func(l *LoggerConfiguration) { | ||
l.response = NewResponseLoggerConfiguration(options...) | ||
} | ||
} | ||
|
||
// messageLoggerConfiguration represents options for logging HTTP message details. | ||
type messageLoggerConfiguration struct { | ||
// Indicates whether the message body should be logged. | ||
body bool | ||
// Indicates whether the message headers should be logged. | ||
headers bool | ||
// Array of headers not to be displayed in logging. | ||
excludeHeaders []string | ||
// Array of headers to be displayed in logging. | ||
includeHeaders []string | ||
// Array of headers which values are non-sensitive to display in logging. | ||
whitelistHeaders []string | ||
} | ||
|
||
func defaultMessageLoggerConfiguration() messageLoggerConfiguration { | ||
return messageLoggerConfiguration{ | ||
body: false, | ||
headers: false, | ||
excludeHeaders: []string{}, | ||
includeHeaders: []string{}, | ||
whitelistHeaders: []string{}, | ||
} | ||
} |
Oops, something went wrong.