From 10536f71def2026279285999779023d98f505e56 Mon Sep 17 00:00:00 2001
From: panwenxing <panwenxing@huawei.com>
Date: Fri, 19 Oct 2018 15:00:34 +0800
Subject: [PATCH] runc: runc logs forwarding to syslog

reason:runc logs forwarding to syslog and using the config "--log-level" to control the number of logs

Change-Id: Ia93f6f5c56131ea8558c4b7b7e5c4bec827a1bad

Conflicts:
        libcontainer/container_linux.go
        libcontainer/process_linux.go
        libcontainer/state_linux.go
---
 create.go                                     |   2 +-
 libcontainer/configs/config.go                |  70 +++++++++---
 libcontainer/container_linux.go               |   2 +-
 main.go                                       |  20 ++++
 vendor/github.com/sirupsen/logrus/Checklist   |   1 +
 .../logrus/hooks/airbrake/airbrake.go         |  54 ++++++++++
 .../sirupsen/logrus/hooks/bugsnag/bugsnag.go  |  68 ++++++++++++
 .../logrus/hooks/papertrail/README.md         |  28 +++++
 .../logrus/hooks/papertrail/papertrail.go     |  55 ++++++++++
 .../sirupsen/logrus/hooks/sentry/README.md    |  61 +++++++++++
 .../sirupsen/logrus/hooks/sentry/sentry.go    | 100 ++++++++++++++++++
 .../sirupsen/logrus/hooks/syslog/README.md    |  20 ++++
 .../sirupsen/logrus/hooks/syslog/syslog.go    |  59 +++++++++++
 13 files changed, 523 insertions(+), 17 deletions(-)
 create mode 100644 vendor/github.com/sirupsen/logrus/Checklist
 create mode 100644 vendor/github.com/sirupsen/logrus/hooks/airbrake/airbrake.go
 create mode 100644 vendor/github.com/sirupsen/logrus/hooks/bugsnag/bugsnag.go
 create mode 100644 vendor/github.com/sirupsen/logrus/hooks/papertrail/README.md
 create mode 100644 vendor/github.com/sirupsen/logrus/hooks/papertrail/papertrail.go
 create mode 100644 vendor/github.com/sirupsen/logrus/hooks/sentry/README.md
 create mode 100644 vendor/github.com/sirupsen/logrus/hooks/sentry/sentry.go
 create mode 100644 vendor/github.com/sirupsen/logrus/hooks/syslog/README.md
 create mode 100644 vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go

diff --git a/create.go b/create.go
index 97854b8..46ef1ef 100644
--- a/create.go
+++ b/create.go
@@ -2,9 +2,9 @@ package main
 
 import (
 	"fmt"
-	"os"
 
 	"github.com/urfave/cli"
+	"os"
 )
 
 var createCommand = cli.Command{
diff --git a/libcontainer/configs/config.go b/libcontainer/configs/config.go
index 72910ff..9076846 100644
--- a/libcontainer/configs/config.go
+++ b/libcontainer/configs/config.go
@@ -4,15 +4,22 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
-	"os/exec"
-	"time"
-
 	"github.com/sirupsen/logrus"
-
 	"github.com/opencontainers/runc/libcontainer/devices"
 	"github.com/opencontainers/runtime-spec/specs-go"
+	"os/exec"
+	"time"
+)
+
+const (
+	minHookTimeOut     = 0
+	defaultHookTimeOut = 5 * time.Second
+	maxHookTimeOut     = 120 * time.Second
+	//the runc default timeout is 120s, so set the defaultWarnTime to 80% of the default timeout.
+	defaultWarnTime = 96 * time.Second
 )
 
+
 type Rlimit struct {
 	Type int    `json:"type"`
 	Hard uint64 `json:"hard"`
@@ -279,11 +286,11 @@ type Capabilities struct {
 
 func (hooks HookList) RunHooks(state *specs.State) error {
 	for i, h := range hooks {
-		logrus.Infof("run hooks %d:%T", i, h)
+		logrus.Infof("run hooks %d:%T, ContainerId: %s", i, h, state.ID)
 		if err := h.Run(state); err != nil {
-			return fmt.Errorf("error running hook #%d: %w", i, err)
+			return fmt.Errorf("error running hook #%d: %w, ContainerId: %s", i, err, state.ID)
 		}
-		logrus.Infof("hooks %d:%T done", i, h)
+		logrus.Infof("hooks %d:%T done, ContainerId: %s", i, h, state.ID)
 	}
 
 	return nil
@@ -388,29 +395,62 @@ func (c Command) Run(s *specs.State) error {
 		Stdout: &stdout,
 		Stderr: &stderr,
 	}
+	startTime := time.Now()
 	if err := cmd.Start(); err != nil {
 		return err
 	}
+	if c.Timeout != nil && *c.Timeout <= minHookTimeOut {
+		*c.Timeout = defaultHookTimeOut
+		logrus.Warnf("hook timeout should not be negative or zero, set hook timeout as 5s")
+	}
+	if c.Timeout != nil && *c.Timeout > maxHookTimeOut {
+		logrus.Warnf("hook timeout: %s is too long, use 120s as timeout. ContainerID: %s", *c.Timeout, s.ID)
+		*c.Timeout = maxHookTimeOut
+	}
 	errC := make(chan error, 1)
+	var (
+		timerCh  <-chan time.Time
+		warnTime = defaultWarnTime.Seconds()
+	)
 	go func() {
 		err := cmd.Wait()
 		if err != nil {
 			err = fmt.Errorf("error running hook: %w, stdout: %s, stderr: %s", err, stdout.String(), stderr.String())
 		}
+		elapsedTime := time.Since(startTime)
+		logrus.Infof("hook spends time %s, ContainerID: %s", elapsedTime, s.ID)
+		if c.Timeout != nil {
+			if elapsedTime.Seconds() >= (c.Timeout.Seconds() * 0.8) {
+				logrus.Warnf("elapsed %s, more than 80%% of the timeout %s, ContainerID: %s", elapsedTime, c.Timeout, s.ID)
+			}
+		} else {
+			if elapsedTime.Seconds() >= warnTime {
+				logrus.Warnf("elapsed %s, more than 80%% of the default timeout 120s, ContainerID: %s", elapsedTime, s.ID)
+			}
+		}
 		errC <- err
 	}()
-	var timerCh <-chan time.Time
 	if c.Timeout != nil {
 		timer := time.NewTimer(*c.Timeout)
 		defer timer.Stop()
 		timerCh = timer.C
+		warnTime = (*c.Timeout).Seconds() * 0.8
 	}
-	select {
-	case err := <-errC:
-		return err
-	case <-timerCh:
-		_ = cmd.Process.Kill()
-		<-errC
-		return fmt.Errorf("hook ran past specified timeout of %.1fs", c.Timeout.Seconds())
+	timeAfter := time.After(time.Duration(warnTime) * time.Second)
+	for {
+		select {
+		case err := <-errC:
+			return err
+		case <-timerCh:
+			cmd.Process.Kill()
+			cmd.Wait()
+			return fmt.Errorf("hook ran past specified timeout of %.1fs", c.Timeout.Seconds())
+		case <-timeAfter:
+			if c.Timeout != nil {
+				logrus.Warnf("hook ran more than 80%% of the timeout %s, ContainerID: %s", *c.Timeout, s.ID)
+			} else {
+				logrus.Warnf("hook ran more than 80%% of the default timeout 120s, ContainerID: %s", s.ID)
+			}
+		}
 	}
 }
diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
index f8ce1cc..8fbd04d 100644
--- a/libcontainer/container_linux.go
+++ b/libcontainer/container_linux.go
@@ -370,7 +370,7 @@ func (c *linuxContainer) start(process *Process) (retErr error) {
 
 			if err := c.config.Hooks[configs.Poststart].RunHooks(s); err != nil {
 				if err := ignoreTerminateErrors(parent.terminate()); err != nil {
-					logrus.Warn(fmt.Errorf("error running poststart hook: %w", err))
+					logrus.Warn(fmt.Errorf("error running poststart hook: %w, ContainerId: %s", err, s.ID))
 				}
 				return err
 			}
diff --git a/main.go b/main.go
index 4d66638..9e14976 100644
--- a/main.go
+++ b/main.go
@@ -100,6 +100,10 @@ func main() {
 			Value: root,
 			Usage: "root directory for storage of container state (this should be located in tmpfs)",
 		},
+		cli.StringFlag{
+			Name:  "log-level",
+			Usage: "set the logging level [debug, info, warn, error, fatal, panic]",
+		},
 		cli.StringFlag{
 			Name:  "criu",
 			Value: "criu",
@@ -211,7 +215,23 @@ func configLogrus(context *cli.Context) error {
 			return err
 		}
 		logrus.SetOutput(f)
+		hook, serr := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO|syslog.LOG_USER, "docker-runc")
+		if serr != nil {
+			fmt.Fprint(f, fmt.Sprintf("Leo: new syslog hook get %s", serr))
+		}
+		logrus.AddHook(hook)
+	}
+	if logLevel := context.GlobalString("log-level"); logLevel != "" {
+		lvl, err := logrus.ParseLevel(logLevel)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "Unable to parse logging level: %s\n", logLevel)
+			os.Exit(1)
+		}
+		logrus.SetLevel(lvl)
 	}
 
+		if context.GlobalBool("debug") {
+		logrus.SetLevel(logrus.DebugLevel)
+	}
 	return nil
 }
diff --git a/vendor/github.com/sirupsen/logrus/Checklist b/vendor/github.com/sirupsen/logrus/Checklist
new file mode 100644
index 0000000..7117b24
--- /dev/null
+++ b/vendor/github.com/sirupsen/logrus/Checklist
@@ -0,0 +1 @@
+imported from runc v1.0.0-Release Candidate 2: Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks
diff --git a/vendor/github.com/sirupsen/logrus/hooks/airbrake/airbrake.go b/vendor/github.com/sirupsen/logrus/hooks/airbrake/airbrake.go
new file mode 100644
index 0000000..b0502c3
--- /dev/null
+++ b/vendor/github.com/sirupsen/logrus/hooks/airbrake/airbrake.go
@@ -0,0 +1,54 @@
+package airbrake
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/tobi/airbrake-go"
+)
+
+// AirbrakeHook to send exceptions to an exception-tracking service compatible
+// with the Airbrake API.
+type airbrakeHook struct {
+	APIKey      string
+	Endpoint    string
+	Environment string
+}
+
+func NewHook(endpoint, apiKey, env string) *airbrakeHook {
+	return &airbrakeHook{
+		APIKey:      apiKey,
+		Endpoint:    endpoint,
+		Environment: env,
+	}
+}
+
+func (hook *airbrakeHook) Fire(entry *logrus.Entry) error {
+	airbrake.ApiKey = hook.APIKey
+	airbrake.Endpoint = hook.Endpoint
+	airbrake.Environment = hook.Environment
+
+	var notifyErr error
+	err, ok := entry.Data["error"].(error)
+	if ok {
+		notifyErr = err
+	} else {
+		notifyErr = errors.New(entry.Message)
+	}
+
+	airErr := airbrake.Notify(notifyErr)
+	if airErr != nil {
+		return fmt.Errorf("Failed to send error to Airbrake: %s", airErr)
+	}
+
+	return nil
+}
+
+func (hook *airbrakeHook) Levels() []logrus.Level {
+	return []logrus.Level{
+		logrus.ErrorLevel,
+		logrus.FatalLevel,
+		logrus.PanicLevel,
+	}
+}
diff --git a/vendor/github.com/sirupsen/logrus/hooks/bugsnag/bugsnag.go b/vendor/github.com/sirupsen/logrus/hooks/bugsnag/bugsnag.go
new file mode 100644
index 0000000..d20a0f5
--- /dev/null
+++ b/vendor/github.com/sirupsen/logrus/hooks/bugsnag/bugsnag.go
@@ -0,0 +1,68 @@
+package logrus_bugsnag
+
+import (
+	"errors"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/bugsnag/bugsnag-go"
+)
+
+type bugsnagHook struct{}
+
+// ErrBugsnagUnconfigured is returned if NewBugsnagHook is called before
+// bugsnag.Configure. Bugsnag must be configured before the hook.
+var ErrBugsnagUnconfigured = errors.New("bugsnag must be configured before installing this logrus hook")
+
+// ErrBugsnagSendFailed indicates that the hook failed to submit an error to
+// bugsnag. The error was successfully generated, but `bugsnag.Notify()`
+// failed.
+type ErrBugsnagSendFailed struct {
+	err error
+}
+
+func (e ErrBugsnagSendFailed) Error() string {
+	return "failed to send error to Bugsnag: " + e.err.Error()
+}
+
+// NewBugsnagHook initializes a logrus hook which sends exceptions to an
+// exception-tracking service compatible with the Bugsnag API. Before using
+// this hook, you must call bugsnag.Configure(). The returned object should be
+// registered with a log via `AddHook()`
+//
+// Entries that trigger an Error, Fatal or Panic should now include an "error"
+// field to send to Bugsnag.
+func NewBugsnagHook() (*bugsnagHook, error) {
+	if bugsnag.Config.APIKey == "" {
+		return nil, ErrBugsnagUnconfigured
+	}
+	return &bugsnagHook{}, nil
+}
+
+// Fire forwards an error to Bugsnag. Given a logrus.Entry, it extracts the
+// "error" field (or the Message if the error isn't present) and sends it off.
+func (hook *bugsnagHook) Fire(entry *logrus.Entry) error {
+	var notifyErr error
+	err, ok := entry.Data["error"].(error)
+	if ok {
+		notifyErr = err
+	} else {
+		notifyErr = errors.New(entry.Message)
+	}
+
+	bugsnagErr := bugsnag.Notify(notifyErr)
+	if bugsnagErr != nil {
+		return ErrBugsnagSendFailed{bugsnagErr}
+	}
+
+	return nil
+}
+
+// Levels enumerates the log levels on which the error should be forwarded to
+// bugsnag: everything at or above the "Error" level.
+func (hook *bugsnagHook) Levels() []logrus.Level {
+	return []logrus.Level{
+		logrus.ErrorLevel,
+		logrus.FatalLevel,
+		logrus.PanicLevel,
+	}
+}
diff --git a/vendor/github.com/sirupsen/logrus/hooks/papertrail/README.md b/vendor/github.com/sirupsen/logrus/hooks/papertrail/README.md
new file mode 100644
index 0000000..ae61e92
--- /dev/null
+++ b/vendor/github.com/sirupsen/logrus/hooks/papertrail/README.md
@@ -0,0 +1,28 @@
+# Papertrail Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
+
+[Papertrail](https://papertrailapp.com) provides hosted log management. Once stored in Papertrail, you can [group](http://help.papertrailapp.com/kb/how-it-works/groups/) your logs on various dimensions, [search](http://help.papertrailapp.com/kb/how-it-works/search-syntax) them, and trigger [alerts](http://help.papertrailapp.com/kb/how-it-works/alerts).
+
+In most deployments, you'll want to send logs to Papertrail via their [remote_syslog](http://help.papertrailapp.com/kb/configuration/configuring-centralized-logging-from-text-log-files-in-unix/) daemon, which requires no application-specific configuration. This hook is intended for relatively low-volume logging, likely in managed cloud hosting deployments where installing `remote_syslog` is not possible.
+
+## Usage
+
+You can find your Papertrail UDP port on your [Papertrail account page](https://papertrailapp.com/account/destinations). Substitute it below for `YOUR_PAPERTRAIL_UDP_PORT`.
+
+For `YOUR_APP_NAME`, substitute a short string that will readily identify your application or service in the logs.
+
+```go
+import (
+  "log/syslog"
+  "github.com/Sirupsen/logrus"
+  "github.com/Sirupsen/logrus/hooks/papertrail"
+)
+
+func main() {
+  log       := logrus.New()
+  hook, err := logrus_papertrail.NewPapertrailHook("logs.papertrailapp.com", YOUR_PAPERTRAIL_UDP_PORT, YOUR_APP_NAME)
+
+  if err == nil {
+    log.Hooks.Add(hook)
+  }
+}
+```
diff --git a/vendor/github.com/sirupsen/logrus/hooks/papertrail/papertrail.go b/vendor/github.com/sirupsen/logrus/hooks/papertrail/papertrail.go
new file mode 100644
index 0000000..c0f10c1
--- /dev/null
+++ b/vendor/github.com/sirupsen/logrus/hooks/papertrail/papertrail.go
@@ -0,0 +1,55 @@
+package logrus_papertrail
+
+import (
+	"fmt"
+	"net"
+	"os"
+	"time"
+
+	"github.com/Sirupsen/logrus"
+)
+
+const (
+	format = "Jan 2 15:04:05"
+)
+
+// PapertrailHook to send logs to a logging service compatible with the Papertrail API.
+type PapertrailHook struct {
+	Host    string
+	Port    int
+	AppName string
+	UDPConn net.Conn
+}
+
+// NewPapertrailHook creates a hook to be added to an instance of logger.
+func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook, error) {
+	conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", host, port))
+	return &PapertrailHook{host, port, appName, conn}, err
+}
+
+// Fire is called when a log event is fired.
+func (hook *PapertrailHook) Fire(entry *logrus.Entry) error {
+	date := time.Now().Format(format)
+	msg, _ := entry.String()
+	payload := fmt.Sprintf("<22> %s %s: %s", date, hook.AppName, msg)
+
+	bytesWritten, err := hook.UDPConn.Write([]byte(payload))
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Unable to send log line to Papertrail via UDP. Wrote %d bytes before error: %v", bytesWritten, err)
+		return err
+	}
+
+	return nil
+}
+
+// Levels returns the available logging levels.
+func (hook *PapertrailHook) Levels() []logrus.Level {
+	return []logrus.Level{
+		logrus.PanicLevel,
+		logrus.FatalLevel,
+		logrus.ErrorLevel,
+		logrus.WarnLevel,
+		logrus.InfoLevel,
+		logrus.DebugLevel,
+	}
+}
diff --git a/vendor/github.com/sirupsen/logrus/hooks/sentry/README.md b/vendor/github.com/sirupsen/logrus/hooks/sentry/README.md
new file mode 100644
index 0000000..19e58bb
--- /dev/null
+++ b/vendor/github.com/sirupsen/logrus/hooks/sentry/README.md
@@ -0,0 +1,61 @@
+# Sentry Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
+
+[Sentry](https://getsentry.com) provides both self-hosted and hosted
+solutions for exception tracking.
+Both client and server are
+[open source](https://github.com/getsentry/sentry).
+
+## Usage
+
+Every sentry application defined on the server gets a different
+[DSN](https://www.getsentry.com/docs/). In the example below replace
+`YOUR_DSN` with the one created for your application.
+
+```go
+import (
+  "github.com/Sirupsen/logrus"
+  "github.com/Sirupsen/logrus/hooks/sentry"
+)
+
+func main() {
+  log       := logrus.New()
+  hook, err := logrus_sentry.NewSentryHook(YOUR_DSN, []logrus.Level{
+    logrus.PanicLevel,
+    logrus.FatalLevel,
+    logrus.ErrorLevel,
+  })
+
+  if err == nil {
+    log.Hooks.Add(hook)
+  }
+}
+```
+
+## Special fields
+
+Some logrus fields have a special meaning in this hook,
+these are server_name and logger.
+When logs are sent to sentry these fields are treated differently.
+- server_name (also known as hostname) is the name of the server which
+is logging the event (hostname.example.com)
+- logger is the part of the application which is logging the event.
+In go this usually means setting it to the name of the package.
+
+## Timeout
+
+`Timeout` is the time the sentry hook will wait for a response
+from the sentry server.
+
+If this time elapses with no response from
+the server an error will be returned.
+
+If `Timeout` is set to 0 the SentryHook will not wait for a reply
+and will assume a correct delivery.
+
+The SentryHook has a default timeout of `100 milliseconds` when created
+with a call to `NewSentryHook`. This can be changed by assigning a value to the `Timeout` field:
+
+```go
+hook, _ := logrus_sentry.NewSentryHook(...)
+hook.Timeout = 20*time.Second
+```
diff --git a/vendor/github.com/sirupsen/logrus/hooks/sentry/sentry.go b/vendor/github.com/sirupsen/logrus/hooks/sentry/sentry.go
new file mode 100644
index 0000000..379f281
--- /dev/null
+++ b/vendor/github.com/sirupsen/logrus/hooks/sentry/sentry.go
@@ -0,0 +1,100 @@
+package logrus_sentry
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/getsentry/raven-go"
+)
+
+var (
+	severityMap = map[logrus.Level]raven.Severity{
+		logrus.DebugLevel: raven.DEBUG,
+		logrus.InfoLevel:  raven.INFO,
+		logrus.WarnLevel:  raven.WARNING,
+		logrus.ErrorLevel: raven.ERROR,
+		logrus.FatalLevel: raven.FATAL,
+		logrus.PanicLevel: raven.FATAL,
+	}
+)
+
+func getAndDel(d logrus.Fields, key string) (string, bool) {
+	var (
+		ok  bool
+		v   interface{}
+		val string
+	)
+	if v, ok = d[key]; !ok {
+		return "", false
+	}
+
+	if val, ok = v.(string); !ok {
+		return "", false
+	}
+	delete(d, key)
+	return val, true
+}
+
+// SentryHook delivers logs to a sentry server.
+type SentryHook struct {
+	// Timeout sets the time to wait for a delivery error from the sentry server.
+	// If this is set to zero the server will not wait for any response and will
+	// consider the message correctly sent
+	Timeout time.Duration
+
+	client *raven.Client
+	levels []logrus.Level
+}
+
+// NewSentryHook creates a hook to be added to an instance of logger
+// and initializes the raven client.
+// This method sets the timeout to 100 milliseconds.
+func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
+	client, err := raven.NewClient(DSN, nil)
+	if err != nil {
+		return nil, err
+	}
+	return &SentryHook{100 * time.Millisecond, client, levels}, nil
+}
+
+// Called when an event should be sent to sentry
+// Special fields that sentry uses to give more information to the server
+// are extracted from entry.Data (if they are found)
+// These fields are: logger and server_name
+func (hook *SentryHook) Fire(entry *logrus.Entry) error {
+	packet := &raven.Packet{
+		Message:   entry.Message,
+		Timestamp: raven.Timestamp(entry.Time),
+		Level:     severityMap[entry.Level],
+		Platform:  "go",
+	}
+
+	d := entry.Data
+
+	if logger, ok := getAndDel(d, "logger"); ok {
+		packet.Logger = logger
+	}
+	if serverName, ok := getAndDel(d, "server_name"); ok {
+		packet.ServerName = serverName
+	}
+	packet.Extra = map[string]interface{}(d)
+
+	_, errCh := hook.client.Capture(packet, nil)
+	timeout := hook.Timeout
+	if timeout != 0 {
+		timeoutCh := time.After(timeout)
+		select {
+		case err := <-errCh:
+			return err
+		case <-timeoutCh:
+			return fmt.Errorf("no response from sentry server in %s", timeout)
+		}
+	}
+	return nil
+}
+
+// Levels returns the available logging levels.
+func (hook *SentryHook) Levels() []logrus.Level {
+	return hook.levels
+}
diff --git a/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md b/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md
new file mode 100644
index 0000000..4dbb8e7
--- /dev/null
+++ b/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md
@@ -0,0 +1,20 @@
+# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>
+
+## Usage
+
+```go
+import (
+  "log/syslog"
+  "github.com/Sirupsen/logrus"
+  logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
+)
+
+func main() {
+  log       := logrus.New()
+  hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
+
+  if err == nil {
+    log.Hooks.Add(hook)
+  }
+}
+```
diff --git a/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go b/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go
new file mode 100644
index 0000000..b6fa374
--- /dev/null
+++ b/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go
@@ -0,0 +1,59 @@
+package logrus_syslog
+
+import (
+	"fmt"
+	"github.com/Sirupsen/logrus"
+	"log/syslog"
+	"os"
+)
+
+// SyslogHook to send logs via syslog.
+type SyslogHook struct {
+	Writer        *syslog.Writer
+	SyslogNetwork string
+	SyslogRaddr   string
+}
+
+// Creates a hook to be added to an instance of logger. This is called with
+// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")`
+// `if err == nil { log.Hooks.Add(hook) }`
+func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) {
+	w, err := syslog.Dial(network, raddr, priority, tag)
+	return &SyslogHook{w, network, raddr}, err
+}
+
+func (hook *SyslogHook) 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:
+		return hook.Writer.Debug(line)
+	default:
+		return nil
+	}
+}
+
+func (hook *SyslogHook) Levels() []logrus.Level {
+	return []logrus.Level{
+		logrus.PanicLevel,
+		logrus.FatalLevel,
+		logrus.ErrorLevel,
+		logrus.WarnLevel,
+		logrus.InfoLevel,
+		logrus.DebugLevel,
+	}
+}
-- 
2.33.0

