forked from TrueCloudLab/frostfs-node
[#736] logger: Add journald support
Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
This commit is contained in:
parent
053a195ac2
commit
9d5c42b14a
11 changed files with 104 additions and 6 deletions
|
@ -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)
|
||||||
|
|
|
@ -96,7 +96,8 @@ type applicationConfiguration struct {
|
||||||
_read bool
|
_read bool
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"logger": {
|
"logger": {
|
||||||
"level": "debug"
|
"level": "debug",
|
||||||
|
"destination": "journald"
|
||||||
},
|
},
|
||||||
"pprof": {
|
"pprof": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|
|
@ -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
2
go.mod
|
@ -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
BIN
go.sum
Binary file not shown.
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue