From 0602c483e795c9e69035b25074f3eacb749ef1c8 Mon Sep 17 00:00:00 2001 From: Maxim Zakharov Date: Thu, 10 Jan 2019 15:01:09 +1100 Subject: [PATCH] Add systemlog hook Signed-off-by: Maxim Zakharov --- hooks/systemlog/README.md | 57 +++++++++++++++++++++++++ hooks/systemlog/systemlog_nacl.go | 27 ++++++++++++ hooks/systemlog/systemlog_plan9.go | 27 ++++++++++++ hooks/systemlog/systemlog_test.go | 26 +++++++++++ hooks/systemlog/systemlog_unixes.go | 53 +++++++++++++++++++++++ hooks/systemlog/systemlog_windows.go | 64 ++++++++++++++++++++++++++++ 6 files changed, 254 insertions(+) create mode 100644 hooks/systemlog/README.md create mode 100644 hooks/systemlog/systemlog_nacl.go create mode 100644 hooks/systemlog/systemlog_plan9.go create mode 100644 hooks/systemlog/systemlog_test.go create mode 100644 hooks/systemlog/systemlog_unixes.go create mode 100644 hooks/systemlog/systemlog_windows.go diff --git a/hooks/systemlog/README.md b/hooks/systemlog/README.md new file mode 100644 index 0000000..0d8e892 --- /dev/null +++ b/hooks/systemlog/README.md @@ -0,0 +1,57 @@ +# System log Hooks for Logrus :walrus: + +## Usage + +```go +import ( + "log/syslog" + "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus/hooks/systemlog" +) + +func main() { + log := logrus.New() + hook, err := systemlog.NewSystemlogHook("udp", "localhost:514", "") + + if err == nil { + log.Hooks.Add(hook) + } +} +``` + +If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). Just assign empty string to the first two parameters of `NewSystemlogHook`. It should look like the following. + +```go +import ( + "log/syslog" + "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus/hooks/systemlog" +) + +func main() { + log := logrus.New() + hook, err := systemlog.NewSyslogHook("", "", "") + + if err == nil { + log.Hooks.Add(hook) + } +} +``` + +On Windows it connects to event log. You may use third parameter to specify your event source. +```go +import ( + "log/syslog" + "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus/hooks/systemlog" +) + +func main() { + log := logrus.New() + hook, err := systemlog.NewSyslogHook("", "localhost", "MySource") + + if err == nil { + log.Hooks.Add(hook) + } +} +``` diff --git a/hooks/systemlog/systemlog_nacl.go b/hooks/systemlog/systemlog_nacl.go new file mode 100644 index 0000000..3c279e2 --- /dev/null +++ b/hooks/systemlog/systemlog_nacl.go @@ -0,0 +1,27 @@ +// +build nacl + +// FIX IT: this is a stub to allow build on nacl. +// Need to be implemented according nacl system facilities. + +package systemlog + +import ( + "github.com/sirupsen/logrus" +) + +// SystemlogHook to do nothing on syslogless systems. +type SystemlogHook struct { +} + +// Creates a stub hook to be added to an instance of logger on syslogless systems. +func NewSystemlogHook(network, raddr string, tag string) (*SystemlogHook, error) { + return &SystemlogHook{}, nil +} + +func (hook *SystemlogHook) Fire(entry *logrus.Entry) error { + return nil +} + +func (hook *SystemlogHook) Levels() []logrus.Level { + return make([]logrus.Level, 0, 0) +} diff --git a/hooks/systemlog/systemlog_plan9.go b/hooks/systemlog/systemlog_plan9.go new file mode 100644 index 0000000..2f397d9 --- /dev/null +++ b/hooks/systemlog/systemlog_plan9.go @@ -0,0 +1,27 @@ +// +build plan9 + +// FIX IT: this is a stub to allow build on plan9. +// Need to be implemented according plan9 system facilities. + +package lachesis_log + +import ( + "github.com/sirupsen/logrus" +) + +// SystemlogHook to do nothing on syslogless systems. +type SystemlogHook struct { +} + +// Creates a stub hook to be added to an instance of logger on syslogless systems. +func NewSystemlogHook(network, raddr string, tag string) (*SystemlogHook, error) { + return &SystemlogHook{}, nil +} + +func (hook *SystemlogHook) Fire(entry *logrus.Entry) error { + return nil +} + +func (hook *SystemlogHook) Levels() []logrus.Level { + return make([]logrus.Level, 0, 0) +} diff --git a/hooks/systemlog/systemlog_test.go b/hooks/systemlog/systemlog_test.go new file mode 100644 index 0000000..4885a84 --- /dev/null +++ b/hooks/systemlog/systemlog_test.go @@ -0,0 +1,26 @@ +package systemlog + +import ( + "testing" + + "github.com/sirupsen/logrus" +) + +func TestLocalhostAddAndPrint(t *testing.T) { + log := logrus.New() + hook, err := NewSystemlogHook("udp", "localhost:514", "systemlog-test") + + if err != nil { + t.Errorf("Unable to connect to local syslog.") + } + + log.Hooks.Add(hook) + + for _, level := range hook.Levels() { + if len(log.Hooks[level]) != 1 { + t.Errorf("SystemlogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level])) + } + } + + log.Info("Congratulations!") +} diff --git a/hooks/systemlog/systemlog_unixes.go b/hooks/systemlog/systemlog_unixes.go new file mode 100644 index 0000000..d4d9f7a --- /dev/null +++ b/hooks/systemlog/systemlog_unixes.go @@ -0,0 +1,53 @@ +// +build !windows,!nacl,!plan9 + +package systemlog + +import ( + "fmt" + "log/syslog" + "os" + + "github.com/sirupsen/logrus" +) + +// SystemlogHook to send logs via syslog. +type SystemlogHook struct { + Writer *syslog.Writer +} + +// Creates a hook to be added to an instance of logger. This is called with +// `hook, err := NewSystemlogHook("udp", "localhost:514", "")` +// `if err == nil { log.Hooks.Add(hook) }` +func NewSystemlogHook(network, raddr string, tag string) (*SystemlogHook, error) { + w, err := syslog.Dial(network, raddr, syslog.LOG_INFO, tag) + return &SystemlogHook{w,}, err +} + +func (hook *SystemlogHook) Fire(entry *logrus.Entry) error { + line, err := entry.String() + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err) + return err + } + + switch entry.Level { + case logrus.PanicLevel: + return hook.Writer.Crit(line) + case logrus.FatalLevel: + return hook.Writer.Crit(line) + case logrus.ErrorLevel: + return hook.Writer.Err(line) + case logrus.WarnLevel: + return hook.Writer.Warning(line) + case logrus.InfoLevel: + return hook.Writer.Info(line) + case logrus.DebugLevel, logrus.TraceLevel: + return hook.Writer.Debug(line) + default: + return nil + } +} + +func (hook *SystemlogHook) Levels() []logrus.Level { + return logrus.AllLevels +} diff --git a/hooks/systemlog/systemlog_windows.go b/hooks/systemlog/systemlog_windows.go new file mode 100644 index 0000000..dfa3b86 --- /dev/null +++ b/hooks/systemlog/systemlog_windows.go @@ -0,0 +1,64 @@ +// +build windows + +package systemlog + +import ( + "fmt" + "strings" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/svc/eventlog" + "github.com/sirupsen/logrus" +) + +// SystemlogHook to send logs via syslog. +type SystemlogHook struct { + Writer *eventlog.Log +} + +// Creates a hook to be added to an instance of logger. This is called with +// `hook, err := NewSystemlogHook("", "localhost", "MySource")` +// `if err == nil { log.Hooks.Add(hook) }` +func NewSystemlogHook(network, raddr string, src string) (*SystemlogHook, error) { + // Continue if we receive "registry key already exists" or if we get + // ERROR_ACCESS_DENIED so that we can log without administrative permissions + // for pre-existing eventlog sources. + if err := eventlog.InstallAsEventCreate(src, eventlog.Info|eventlog.Warning|eventlog.Error); err != nil { + if !strings.Contains(err.Error(), "registry key already exists") && err != windows.ERROR_ACCESS_DENIED { + return nil, err + } + } + el, err := eventlog.OpenRemote(raddr, src) + if err != nil { + return nil, err + } + return &SystemlogHook{el,}, err +} + +func (hook *SystemlogHook) Fire(entry *logrus.Entry) error { + line, err := entry.String() + if err != nil { + return fmt.Errorf("Unable to read entry, %v", err) + } + + switch entry.Level { + case logrus.PanicLevel: + return hook.Writer.Error(1, line) + case logrus.FatalLevel: + return hook.Writer.Error(2, line) + case logrus.ErrorLevel: + return hook.Writer.Error(3, line) + case logrus.WarnLevel: + return hook.Writer.Warning(4, line) + case logrus.InfoLevel: + return hook.Writer.Info(5, line) + case logrus.DebugLevel, logrus.TraceLevel: + return hook.Writer.Info(6, line) + default: + return nil + } +} + +func (hook *SystemlogHook) Levels() []logrus.Level { + return logrus.AllLevels +}