Skip to content

Commit

Permalink
feat: configurable log prefix
Browse files Browse the repository at this point in the history
For adjusting prefix of output, e.g. to add prefix string or log
formatting flags.

Format:
- prefix: string until first occurance of %
- flag: combination of flags

Flags:
- %d - date
- %t - time
- %m - microseconds
- %l - long file name
- %s - short file name
- %z - use UTC
- %p - move the "prefix" from the beginning of the line to before the message
- %S - datetime  - same as "%d %t"

Basically the meaning is the same as in std log library:
  https://pkg.go.dev/log#pkg-constants

Signed-off-by: Petr Vobornik <[email protected]>
  • Loading branch information
pvoborni committed Nov 10, 2023
1 parent b50fe0e commit e7d4b2d
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 15 deletions.
1 change: 1 addition & 0 deletions .devcontainer/host-metering.conf
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ write_retry_max_int_sec=2
metrics_wal_path=./mocks/cpumetrics
metrics_max_age_sec=30
log_level=DEBUG
log_prefix=%S
10 changes: 10 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
DefaultMetricsWALPath = "/var/run/host-metering/metrics"
DefaultLogLevel = "INFO"
DefaultLogPath = "" //Default to stderr, will be logged in journal.
DefaultLogPrefix = ""
)

type Config struct {
Expand All @@ -42,6 +43,7 @@ type Config struct {
MetricsWALPath string
LogLevel string // one of "ERROR", "WARN", "INFO", "DEBUG", "TRACE"
LogPath string
LogPrefix string
}

func NewConfig() *Config {
Expand All @@ -60,6 +62,7 @@ func NewConfig() *Config {
MetricsWALPath: DefaultMetricsWALPath,
LogLevel: DefaultLogLevel,
LogPath: DefaultLogPath,
LogPrefix: DefaultLogPrefix,
}
}

Expand All @@ -81,6 +84,7 @@ func (c *Config) String() string {
fmt.Sprintf("| MetricsWALPath: %s", c.MetricsWALPath),
fmt.Sprintf("| LogLevel: %s", c.LogLevel),
fmt.Sprintf("| LogPath: %s", c.LogPath),
fmt.Sprintf("| LogPrefix: %s", c.LogPrefix),
}, "\n")
}

Expand Down Expand Up @@ -138,6 +142,9 @@ func (c *Config) UpdateFromEnvVars() error {
if v := os.Getenv("HOST_METERING_LOG_PATH"); v != "" {
c.LogPath = v
}
if v := os.Getenv("HOST_METERING_LOG_PREFIX"); v != "" {
c.LogPrefix = v
}
return multiError.ErrorOrNil()
}

Expand Down Expand Up @@ -252,6 +259,9 @@ func (c *Config) UpdateFromConfigFile(path string) error {
if v, ok := config[section]["log_path"]; ok {
c.LogPath = v
}
if v, ok := config[section]["log_prefix"]; ok {
c.LogPrefix = v
}

return multiError.ErrorOrNil()
}
Expand Down
14 changes: 10 additions & 4 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ func TestDefaultConfig(t *testing.T) {
"| MetricsMaxAgeSec: 5400\n" +
"| MetricsWALPath: /var/run/host-metering/metrics\n" +
"| LogLevel: INFO\n" +
"| LogPath: \n"
"| LogPath: \n" +
"| LogPrefix: \n"

// Create the default configuration.
c := NewConfig()
Expand Down Expand Up @@ -71,7 +72,8 @@ func TestConfigFile(t *testing.T) {
"| MetricsMaxAgeSec: 700\n" +
"| MetricsWALPath: /tmp/metrics\n" +
"| LogLevel: ERROR\n" +
"| LogPath: /tmp/log\n"
"| LogPath: /tmp/log\n" +
"| LogPrefix: %d%t\n"

// Update the configuration from a valid config file.
fileContent := "[host-metering]\n" +
Expand All @@ -90,7 +92,8 @@ func TestConfigFile(t *testing.T) {
"metrics_max_age_sec = 700\n" +
"metrics_wal_path = /tmp/metrics\n" +
"log_level = ERROR\n" +
"log_path = /tmp/log\n"
"log_path = /tmp/log\n" +
"log_prefix = %d%t\n"

c := NewConfig()

Expand Down Expand Up @@ -148,7 +151,8 @@ func TestEnvVariables(t *testing.T) {
"| MetricsMaxAgeSec: 700\n" +
"| MetricsWALPath: /tmp/metrics\n" +
"| LogLevel: ERROR\n" +
"| LogPath: /tmp/log\n"
"| LogPath: /tmp/log\n" +
"| LogPrefix: %d\n"

// Set valid environment variables.
t.Setenv("HOST_METERING_WRITE_URL", "http://test/url")
Expand All @@ -165,6 +169,7 @@ func TestEnvVariables(t *testing.T) {
t.Setenv("HOST_METERING_METRICS_WAL_PATH", "/tmp/metrics")
t.Setenv("HOST_METERING_LOG_LEVEL", "ERROR")
t.Setenv("HOST_METERING_LOG_PATH", "/tmp/log")
t.Setenv("HOST_METERING_LOG_PREFIX", "%d")

// Environment variables are set. Change the defaults.
c := NewConfig()
Expand Down Expand Up @@ -218,6 +223,7 @@ func clearEnvironment() {
_ = os.Unsetenv("HOST_METERING_METRICS_WAL_PATH")
_ = os.Unsetenv("HOST_METERING_LOG_LEVEL")
_ = os.Unsetenv("HOST_METERING_LOG_PATH")
_ = os.Unsetenv("HOST_METERING_LOG_PREFIX")
}

func checkError(t *testing.T, err error, message string) {
Expand Down
43 changes: 43 additions & 0 deletions contrib/man/host-metering.1
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,49 @@ Log level. Possible values are: DEBUG, INFO, WARN, ERROR, TRACE.
\fBHOST_METERING_LOG_PATH\fR
Path to log file. Default is empty - stderr.

\fBHOST_METERING_LOG_PREFIX\fR
Prefix of log messages. Default is empty. Format: "[PREFIX][FLAG]*"

.RS 4

\fBPREFIX:\fR string until first occurance of %

\fBFLAGS:\fR
.RS 4
.TP
.B %d
Date

.TP
.B %t
Time

.TP
.B %m
Microseconds

.TP
.B %l
Long file name

.TP
.B %s
Short file name

.TP
.B %z
Use UTC

.TP
.B %p
Move the "PREFIX" from the beginning of the line to before the message

.TP
.B %S
Datetime (same as "%d %t")
.RE
.RE

.SH "FILES"
.PP
\fI/etc/host-metering.conf\fR
Expand Down
43 changes: 43 additions & 0 deletions contrib/man/host-metering.conf.5
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,49 @@ log_path (string)
Path to log file. Default is empty - stderr.
.RE

.PP
log_prefix (string)
.RS 4
Prefix of log messages. Default is empty. Format: "[PREFIX][FLAG]*"

\fBPREFIX:\fR string until first occurance of %

\fBFLAGS:\fR
.RS 4
.TP
.B %d
Date

.TP
.B %t
Time

.TP
.B %m
Microseconds

.TP
.B %l
Long file name

.TP
.B %s
Short file name

.TP
.B %z
Use UTC

.TP
.B %p
Move the "PREFIX" from the beginning of the line to before the message

.TP
.B %S
Datetime (same as "%d %t")
.RE
.RE

.SH "EXAMPLES"
.PP
1\&. The following example shows how to switch the logging to DEBUG level\&.
Expand Down
51 changes: 43 additions & 8 deletions logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import (
"strings"
"time"

std_log "log"

go_log "git.sr.ht/~spc/go-log"
)

const defaultLogFormat = 0
const defaultLogPrefix = ""

const (
DebugLevel = "DEBUG"
Expand Down Expand Up @@ -82,21 +85,16 @@ type Logger interface {
}

func InitDefaultLogger() Logger {
return go_log.New(os.Stderr, "", defaultLogFormat, go_log.LevelDebug)
return go_log.New(os.Stderr, defaultLogPrefix, defaultLogFormat, go_log.LevelDebug)
}

func InitLogger(file string, level string, logStructure ...int) error {
func InitLogger(file string, level string, prefix string, flag int) error {
logLevel, err := go_log.ParseLevel(level)

if err != nil {
return err
}

actualLogStructure := defaultLogFormat
if len(logStructure) > 0 {
actualLogStructure = logStructure[0]
}

logFile := os.Stderr
if file != "" {
logFile, err = os.OpenFile(file, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
Expand All @@ -105,7 +103,7 @@ func InitLogger(file string, level string, logStructure ...int) error {
return err
}

log = go_log.New(logFile, "", actualLogStructure, logLevel)
log = go_log.New(logFile, prefix, flag, logLevel)

return nil
}
Expand Down Expand Up @@ -214,6 +212,43 @@ func Traceln(v ...interface{}) {
getLogger().Traceln(v...)
}

func ParseLogPrefix(format string) (prefix string, flag int) {
if !strings.Contains(format, "%") {
return format, defaultLogFormat
}

prefix = format[:strings.Index(format, "%")]
flag = 0

if strings.Contains(format, "%d") {
flag |= std_log.Ldate
}
if strings.Contains(format, "%t") {
flag |= std_log.Ltime
}
if strings.Contains(format, "%m") {
flag |= std_log.Lmicroseconds
}
if strings.Contains(format, "%l") {
flag |= std_log.Llongfile
}
if strings.Contains(format, "%s") {
flag |= std_log.Lshortfile
}
if strings.Contains(format, "%z") {
flag |= std_log.LUTC
}
if strings.Contains(format, "%p") {
flag |= std_log.Lmsgprefix
}
if strings.Contains(format, "%S") {
flag |= std_log.LstdFlags
}

return prefix, flag

}

type LogEntry struct {
Time time.Time
Level string
Expand Down
42 changes: 40 additions & 2 deletions logger/logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"os"
"strings"
"testing"

std_log "log"
)

// Test that logger global functions won't crash even if the logger is not initialized.
Expand Down Expand Up @@ -46,7 +48,7 @@ func TestLoggerGlobalFunctions(t *testing.T) {

// Test initialization of logger with only log level
func TestInitLogger(t *testing.T) {
InitLogger("", DebugLevel)
InitLogger("", DebugLevel, defaultLogPrefix, defaultLogFormat)
if log == nil {
t.Fatalf("logger is not initialized")
}
Expand All @@ -58,7 +60,7 @@ func TestInitLogger(t *testing.T) {
func TestInitLoggerFile(t *testing.T) {
dir := t.TempDir()
path := dir + "/test.log"
InitLogger(path, DebugLevel)
InitLogger(path, DebugLevel, defaultLogPrefix, defaultLogFormat)
if log == nil {
t.Fatalf("logger is not initialized")
}
Expand Down Expand Up @@ -203,6 +205,42 @@ func TestOverridenLogger(t *testing.T) {
}
}

type LogPrefixTestCase struct {
prefix string
expectedPrefix string
expectedFlag int
}

func TestParseLogPrefix(t *testing.T) {
testCases := []LogPrefixTestCase{
{"", "", 0},
{"test", "test", 0},
{"test:", "test:", 0},
{"test: ", "test: ", 0},
{"test: %d", "test: ", std_log.Ldate},
{"test: %d %t", "test: ", std_log.Ldate | std_log.Ltime},
{"test: %d %t %m", "test: ", std_log.Ldate | std_log.Ltime | std_log.Lmicroseconds},
{"test: %S %l", "test: ", std_log.LstdFlags | std_log.Llongfile},
{"test: %S %s", "test: ", std_log.LstdFlags | std_log.Lshortfile},
{"test: %S %z", "test: ", std_log.LstdFlags | std_log.LUTC},
{"test%S %p", "test", std_log.LstdFlags | std_log.Lmsgprefix},
{"test: %S", "test: ", std_log.LstdFlags},
}

for _, tc := range testCases {
t.Run(tc.prefix, func(t *testing.T) {
prefix, flag := ParseLogPrefix(tc.prefix)
if prefix != tc.expectedPrefix {
t.Fatalf("expected prefix: %s got: %s", tc.prefix, prefix)
}
if flag != tc.expectedFlag {
t.Fatalf("expected flag: %d got: %d", tc.expectedFlag, flag)
}
})
}

}

// Helper functions

func clearLogger() {
Expand Down
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ func main() {
}

// initialize the logger according to the given configuration
err = logger.InitLogger(cfg.LogPath, cfg.LogLevel)
logPrefix, logFlag := logger.ParseLogPrefix(cfg.LogPrefix)
err = logger.InitLogger(cfg.LogPath, cfg.LogLevel, logPrefix, logFlag)

if err != nil {
logger.Debugf("Error initializing logger: %s\n", err.Error())
Expand Down

0 comments on commit e7d4b2d

Please sign in to comment.