[#736] logger: Add journald support
All checks were successful
DCO action / DCO (pull_request) Successful in 1m14s
Vulncheck / Vulncheck (pull_request) Successful in 3m28s
Tests and linters / Tests (1.21) (pull_request) Successful in 5m23s
Build / Build Components (1.21) (pull_request) Successful in 5m1s
Build / Build Components (1.20) (pull_request) Successful in 5m8s
Tests and linters / Staticcheck (pull_request) Successful in 5m19s
Tests and linters / Lint (pull_request) Successful in 5m47s
Tests and linters / Tests (1.20) (pull_request) Successful in 7m30s
Tests and linters / Tests with -race (pull_request) Successful in 7m22s

Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
This commit is contained in:
Dmitrii Stepanov 2024-02-08 17:04:58 +03:00
parent 053a195ac2
commit 9d5c42b14a
11 changed files with 104 additions and 6 deletions

View file

@ -68,6 +68,10 @@ func main() {
cfg.GetString("logger.level"), cfg.GetString("logger.level"),
) )
exitErr(err) exitErr(err)
err = logPrm.SetDestination(
cfg.GetString("logger.destination"),
)
exitErr(err)
logPrm.SamplingHook = metrics.LogMetrics().GetSamplingHook() logPrm.SamplingHook = metrics.LogMetrics().GetSamplingHook()
log, err = logger.NewLogger(logPrm) log, err = logger.NewLogger(logPrm)
exitErr(err) exitErr(err)

View file

@ -97,6 +97,7 @@ type applicationConfiguration struct {
LoggerCfg struct { LoggerCfg struct {
level string level string
destination string
} }
EngineCfg struct { EngineCfg struct {
@ -209,6 +210,7 @@ func (a *applicationConfiguration) readConfig(c *config.Config) error {
// Logger // Logger
a.LoggerCfg.level = loggerconfig.Level(c) a.LoggerCfg.level = loggerconfig.Level(c)
a.LoggerCfg.destination = loggerconfig.Destination(c)
// Storage Engine // Storage Engine
@ -1010,6 +1012,11 @@ func (c *cfg) loggerPrm() (*logger.Prm, error) {
// not expected since validation should be performed before // not expected since validation should be performed before
panic(fmt.Sprintf("incorrect log level format: %s", c.LoggerCfg.level)) panic(fmt.Sprintf("incorrect log level format: %s", c.LoggerCfg.level))
} }
err = c.dynamicConfiguration.logger.SetDestination(c.LoggerCfg.destination)
if err != nil {
// not expected since validation should be performed before
panic(fmt.Sprintf("incorrect log destination format: %s", c.LoggerCfg.destination))
}
return c.dynamicConfiguration.logger, nil return c.dynamicConfiguration.logger, nil
} }

View file

@ -5,12 +5,14 @@ import (
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
"git.frostfs.info/TrueCloudLab/frostfs-observability/logging/lokicore/loki" "git.frostfs.info/TrueCloudLab/frostfs-observability/logging/lokicore/loki"
) )
const ( const (
// LevelDefault is a default logger level. // LevelDefault is a default logger level.
LevelDefault = "info" LevelDefault = "info"
DestinationDefault = logger.DestinationStdout
subsection = "logger" subsection = "logger"
lokiSubsection = "loki" lokiSubsection = "loki"
AddressDefault = "localhost:3100" AddressDefault = "localhost:3100"
@ -34,6 +36,22 @@ func Level(c *config.Config) string {
return LevelDefault return LevelDefault
} }
// Destination returns the value of "destination" config parameter
// from "logger" section.
//
// Returns DestinationDefault if the value is not a non-empty string.
func Destination(c *config.Config) string {
v := config.StringSafe(
c.Sub(subsection),
"destination",
)
if v != "" {
return v
}
return DestinationDefault
}
// ToLokiConfig extracts loki config. // ToLokiConfig extracts loki config.
func ToLokiConfig(c *config.Config) loki.Config { func ToLokiConfig(c *config.Config) loki.Config {
hostname, _ := os.Hostname() hostname, _ := os.Hostname()

View file

@ -11,15 +11,15 @@ import (
func TestLoggerSection_Level(t *testing.T) { func TestLoggerSection_Level(t *testing.T) {
t.Run("defaults", func(t *testing.T) { t.Run("defaults", func(t *testing.T) {
v := loggerconfig.Level(configtest.EmptyConfig()) require.Equal(t, loggerconfig.LevelDefault, loggerconfig.Level(configtest.EmptyConfig()))
require.Equal(t, loggerconfig.LevelDefault, v) require.Equal(t, loggerconfig.DestinationDefault, loggerconfig.Destination(configtest.EmptyConfig()))
}) })
const path = "../../../../config/example/node" const path = "../../../../config/example/node"
fileConfigTest := func(c *config.Config) { fileConfigTest := func(c *config.Config) {
v := loggerconfig.Level(c) require.Equal(t, "debug", loggerconfig.Level(c))
require.Equal(t, "debug", v) require.Equal(t, "journald", loggerconfig.Destination(c))
} }
configtest.ForEachFileType(path, fileConfigTest) configtest.ForEachFileType(path, fileConfigTest)

View file

@ -25,6 +25,11 @@ func validateConfig(c *config.Config) error {
return fmt.Errorf("invalid logger level: %w", err) return fmt.Errorf("invalid logger level: %w", err)
} }
err = loggerPrm.SetDestination(loggerconfig.Destination(c))
if err != nil {
return fmt.Errorf("invalid logger destination: %w", err)
}
// shard configuration validation // shard configuration validation
shardNum := 0 shardNum := 0

View file

@ -1,4 +1,5 @@
FROSTFS_LOGGER_LEVEL=debug FROSTFS_LOGGER_LEVEL=debug
FROSTFS_LOGGER_DESTINATION=journald
FROSTFS_PPROF_ENABLED=true FROSTFS_PPROF_ENABLED=true
FROSTFS_PPROF_ADDRESS=localhost:6060 FROSTFS_PPROF_ADDRESS=localhost:6060

View file

@ -1,6 +1,7 @@
{ {
"logger": { "logger": {
"level": "debug" "level": "debug",
"destination": "journald"
}, },
"pprof": { "pprof": {
"enabled": true, "enabled": true,

View file

@ -1,5 +1,6 @@
logger: logger:
level: debug # logger level: one of "debug", "info" (default), "warn", "error", "dpanic", "panic", "fatal" level: debug # logger level: one of "debug", "info" (default), "warn", "error", "dpanic", "panic", "fatal"
destination: journald # logger destination: one of "stdout" (default), "journald"
systemdnotify: systemdnotify:
enabled: true enabled: true

2
go.mod
View file

@ -10,6 +10,7 @@ require (
git.frostfs.info/TrueCloudLab/hrw v1.2.1 git.frostfs.info/TrueCloudLab/hrw v1.2.1
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240129064140-8d21ab2d99d9 git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240129064140-8d21ab2d99d9
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 git.frostfs.info/TrueCloudLab/tzhash v1.8.0
git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02
github.com/cheggaaa/pb v1.0.29 github.com/cheggaaa/pb v1.0.29
github.com/chzyer/readline v1.5.1 github.com/chzyer/readline v1.5.1
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568
@ -29,6 +30,7 @@ require (
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.18.2 github.com/spf13/viper v1.18.2
github.com/ssgreg/journald v1.0.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
go.etcd.io/bbolt v1.3.8 go.etcd.io/bbolt v1.3.8
go.opentelemetry.io/otel v1.22.0 go.opentelemetry.io/otel v1.22.0

BIN
go.sum

Binary file not shown.

View file

@ -1,6 +1,10 @@
package logger package logger
import ( import (
"fmt"
"git.frostfs.info/TrueCloudLab/zapjournald"
"github.com/ssgreg/journald"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
) )
@ -35,8 +39,15 @@ type Prm struct {
SamplingHook func(e zapcore.Entry, sd zapcore.SamplingDecision) SamplingHook func(e zapcore.Entry, sd zapcore.SamplingDecision)
// do not support runtime rereading // do not support runtime rereading
dest string
} }
const (
DestinationUndefined = ""
DestinationStdout = "stdout"
DestinationJournald = "journald"
)
// SetLevelString sets the minimum logging level. Default is // SetLevelString sets the minimum logging level. Default is
// "info". // "info".
// //
@ -48,6 +59,16 @@ func (p *Prm) SetLevelString(s string) error {
return p.level.UnmarshalText([]byte(s)) return p.level.UnmarshalText([]byte(s))
} }
func (p *Prm) SetDestination(d string) error {
if d != DestinationStdout && d != DestinationJournald {
return fmt.Errorf("invalid logger destination %s", d)
}
if p != nil {
p.dest = d
}
return nil
}
// Reload reloads configuration of a connected instance of the Logger. // Reload reloads configuration of a connected instance of the Logger.
// Returns ErrLoggerNotConnected if no connection has been performed. // Returns ErrLoggerNotConnected if no connection has been performed.
// Returns any reconfiguration error from the Logger directly. // Returns any reconfiguration error from the Logger directly.
@ -79,7 +100,17 @@ func NewLogger(prm *Prm) (*Logger, error) {
if prm == nil { if prm == nil {
prm = defaultPrm() prm = defaultPrm()
} }
switch prm.dest {
case DestinationUndefined, DestinationStdout:
return newConsoleLogger(prm)
case DestinationJournald:
return newJournaldLogger(prm)
default:
return nil, fmt.Errorf("unknown destination %s", prm.dest)
}
}
func newConsoleLogger(prm *Prm) (*Logger, error) {
lvl := zap.NewAtomicLevelAt(prm.level) lvl := zap.NewAtomicLevelAt(prm.level)
c := zap.NewProductionConfig() c := zap.NewProductionConfig()
@ -103,6 +134,34 @@ func NewLogger(prm *Prm) (*Logger, error) {
return l, nil return l, nil
} }
func newJournaldLogger(prm *Prm) (*Logger, error) {
lvl := zap.NewAtomicLevelAt(prm.level)
c := zap.NewProductionConfig()
c.Level = lvl
c.Encoding = "console"
c.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
if prm.SamplingHook != nil {
c.Sampling.Hook = prm.SamplingHook
}
encoder := zapjournald.NewPartialEncoder(zapcore.NewConsoleEncoder(c.EncoderConfig), zapjournald.SyslogFields)
core := zapjournald.NewCore(zap.NewAtomicLevelAt(prm.level), encoder, &journald.Journal{}, zapjournald.SyslogFields)
coreWithContext := core.With([]zapcore.Field{
zapjournald.SyslogFacility(zapjournald.LogDaemon),
zapjournald.SyslogIdentifier(),
zapjournald.SyslogPid(),
})
lZap := zap.New(coreWithContext, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)))
l := &Logger{Logger: lZap, lvl: lvl}
prm._log = l
return l, nil
}
func (l *Logger) reload(prm Prm) error { func (l *Logger) reload(prm Prm) error {
l.lvl.SetLevel(prm.level) l.lvl.SetLevel(prm.level)
return nil return nil