diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index 5b15919608..8492a67191 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -55,6 +55,11 @@
"github.com/docker/machine/version"
],
"Deps": [
+ {
+ "ImportPath": "github.com/Sirupsen/logrus",
+ "Comment": "v0.8.7-49-gcdaedc6",
+ "Rev": "cdaedc68f2894175ac2b3221869685602c759e71"
+ },
{
"ImportPath": "github.com/MSOpenTech/azure-sdk-for-go",
"Comment": "v1.1-17-g515f3ec",
diff --git a/cmd/machine.go b/cmd/machine.go
index 9c55a60445..c330d5d10c 100644
--- a/cmd/machine.go
+++ b/cmd/machine.go
@@ -66,7 +66,7 @@ func setDebugOutputLevel() {
// use -v / --verbose TBQH
for _, f := range os.Args {
if f == "-D" || f == "--debug" || f == "-debug" {
- log.IsDebug = true
+ log.SetDebug(true)
}
}
@@ -77,7 +77,7 @@ func setDebugOutputLevel() {
fmt.Fprintf(os.Stderr, "Error parsing boolean value from MACHINE_DEBUG: %s\n", err)
os.Exit(1)
}
- log.IsDebug = showDebug
+ log.SetDebug(showDebug)
}
}
diff --git a/commands/config.go b/commands/config.go
index 0b01f418b0..0303136b69 100644
--- a/commands/config.go
+++ b/commands/config.go
@@ -2,7 +2,6 @@ package commands
import (
"fmt"
- "os"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/check"
@@ -12,7 +11,7 @@ import (
func cmdConfig(c CommandLine, api libmachine.API) error {
// Ensure that log messages always go to stderr when this command is
// being run (it is intended to be run in a subshell)
- log.SetOutWriter(os.Stderr)
+ log.RedirectStdOutToStdErr()
if len(c.Args()) != 1 {
return ErrExpectedOneMachine
diff --git a/commands/env.go b/commands/env.go
index 81890f6efe..1aff42ae10 100644
--- a/commands/env.go
+++ b/commands/env.go
@@ -50,7 +50,7 @@ func cmdEnv(c CommandLine, api libmachine.API) error {
// Ensure that log messages always go to stderr when this command is
// being run (it is intended to be run in a subshell)
- log.SetOutWriter(os.Stderr)
+ log.RedirectStdOutToStdErr()
if c.Bool("unset") {
shellCfg, err = shellCfgUnset(c, api)
@@ -244,7 +244,7 @@ func detectShell() (string, error) {
if shell == "" {
// check for windows env and not bash (i.e. msysgit, etc)
if runtime.GOOS == "windows" {
- log.Printf("On Windows, please specify either 'cmd' or 'powershell' with the --shell flag.\n\n")
+ log.Info("On Windows, please specify either 'cmd' or 'powershell' with the --shell flag.\n\n")
}
return "", ErrUnknownShell
diff --git a/drivers/openstack/client.go b/drivers/openstack/client.go
index b95cd85159..9c03051795 100644
--- a/drivers/openstack/client.go
+++ b/drivers/openstack/client.go
@@ -477,7 +477,7 @@ func (c *GenericClient) Authenticate(d *Driver) error {
return nil
}
- log.WithFields(log.Fields{
+ log.Debug("Authenticating...", map[string]interface{}{
"AuthUrl": d.AuthUrl,
"Insecure": d.Insecure,
"DomainID": d.DomainID,
@@ -485,7 +485,7 @@ func (c *GenericClient) Authenticate(d *Driver) error {
"Username": d.Username,
"TenantName": d.TenantName,
"TenantID": d.TenantId,
- }).Debug("Authenticating...")
+ })
opts := gophercloud.AuthOptions{
IdentityEndpoint: d.AuthUrl,
diff --git a/drivers/openstack/openstack.go b/drivers/openstack/openstack.go
index 297d2b1b99..8deeb063aa 100644
--- a/drivers/openstack/openstack.go
+++ b/drivers/openstack/openstack.go
@@ -278,7 +278,7 @@ func (d *Driver) GetIP() (string, error) {
return d.IPAddress, nil
}
- log.WithField("MachineId", d.MachineId).Debug("Looking for the IP address...")
+ log.Debug("Looking for the IP address...", map[string]string{"MachineId": d.MachineId})
if err := d.initCompute(); err != nil {
return "", err
@@ -306,7 +306,7 @@ func (d *Driver) GetIP() (string, error) {
}
func (d *Driver) GetState() (state.State, error) {
- log.WithField("MachineId", d.MachineId).Debug("Get status for OpenStack instance...")
+ log.Debug("Get status for OpenStack instance...", map[string]string{"MachineId": d.MachineId})
if err := d.initCompute(); err != nil {
return state.None, err
}
@@ -316,10 +316,10 @@ func (d *Driver) GetState() (state.State, error) {
return state.None, err
}
- log.WithFields(log.Fields{
+ log.Debug("State for OpenStack instance", map[string]string{
"MachineId": d.MachineId,
"State": s,
- }).Debug("State for OpenStack instance")
+ })
switch s {
case "ACTIVE":
@@ -365,7 +365,7 @@ func (d *Driver) Create() error {
}
func (d *Driver) Start() error {
- log.WithField("MachineId", d.MachineId).Info("Starting OpenStack instance...")
+ log.Debug("Starting OpenStack instance...", map[string]string{"MachineId": d.MachineId})
if err := d.initCompute(); err != nil {
return err
}
@@ -376,7 +376,7 @@ func (d *Driver) Start() error {
}
func (d *Driver) Stop() error {
- log.WithField("MachineId", d.MachineId).Info("Stopping OpenStack instance...")
+ log.Debug("Stopping OpenStack instance...", map[string]string{"MachineId": d.MachineId})
if err := d.initCompute(); err != nil {
return err
}
@@ -388,7 +388,7 @@ func (d *Driver) Stop() error {
}
func (d *Driver) Remove() error {
- log.WithField("MachineId", d.MachineId).Debug("deleting instance...")
+ log.Debug("deleting instance...", map[string]string{"MachineId": d.MachineId})
log.Info("Deleting OpenStack instance...")
if err := d.initCompute(); err != nil {
return err
@@ -396,7 +396,7 @@ func (d *Driver) Remove() error {
if err := d.client.DeleteInstance(d); err != nil {
return err
}
- log.WithField("Name", d.KeyPairName).Debug("deleting key pair...")
+ log.Debug("deleting key pair...", map[string]string{"Name": d.KeyPairName})
if err := d.client.DeleteKeyPair(d, d.KeyPairName); err != nil {
return err
}
@@ -404,7 +404,7 @@ func (d *Driver) Remove() error {
}
func (d *Driver) Restart() error {
- log.WithField("MachineId", d.MachineId).Info("Restarting OpenStack instance...")
+ log.Info("Restarting OpenStack instance...", map[string]string{"MachineId": d.MachineId})
if err := d.initCompute(); err != nil {
return err
}
@@ -483,10 +483,10 @@ func (d *Driver) resolveIds() error {
}
d.NetworkId = networkID
- log.WithFields(log.Fields{
+ log.Debug("Found network id using its name", map[string]string{
"Name": d.NetworkName,
"ID": d.NetworkId,
- }).Debug("Found network id using its name")
+ })
}
if d.FlavorName != "" {
@@ -504,10 +504,10 @@ func (d *Driver) resolveIds() error {
}
d.FlavorId = flavorID
- log.WithFields(log.Fields{
+ log.Debug("Found flavor id using its name", map[string]string{
"Name": d.FlavorName,
"ID": d.FlavorId,
- }).Debug("Found flavor id using its name")
+ })
}
if d.ImageName != "" {
@@ -525,10 +525,10 @@ func (d *Driver) resolveIds() error {
}
d.ImageId = imageID
- log.WithFields(log.Fields{
+ log.Debug("Found image id using its name", map[string]string{
"Name": d.ImageName,
"ID": d.ImageId,
- }).Debug("Found image id using its name")
+ })
}
if d.FloatingIpPool != "" && !d.ComputeNetwork {
@@ -546,10 +546,10 @@ func (d *Driver) resolveIds() error {
}
d.FloatingIpPoolId = f
- log.WithFields(log.Fields{
+ log.Debug("Found floating IP pool id using its name", map[string]string{
"Name": d.FloatingIpPool,
"ID": d.FloatingIpPoolId,
- }).Debug("Found floating IP pool id using its name")
+ })
}
return nil
@@ -577,7 +577,7 @@ func (d *Driver) initNetwork() error {
func (d *Driver) createSSHKey() error {
sanitizeKeyPairName(&d.KeyPairName)
- log.WithField("Name", d.KeyPairName).Debug("Creating Key Pair...")
+ log.Debug("Creating Key Pair...", map[string]string{"Name": d.KeyPairName})
if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
return err
}
@@ -596,10 +596,10 @@ func (d *Driver) createSSHKey() error {
}
func (d *Driver) createMachine() error {
- log.WithFields(log.Fields{
+ log.Debug("Creating OpenStack instance...", map[string]string{
"FlavorId": d.FlavorId,
"ImageId": d.ImageId,
- }).Debug("Creating OpenStack instance...")
+ })
if err := d.initCompute(); err != nil {
return err
@@ -632,17 +632,17 @@ func (d *Driver) assignFloatingIP() error {
var floatingIP *FloatingIP
- log.WithFields(log.Fields{
+ log.Debugf("Looking for an available floating IP", map[string]string{
"MachineId": d.MachineId,
"Pool": d.FloatingIpPool,
- }).Debugf("Looking for an available floating IP")
+ })
for _, ip := range ips {
if ip.PortId == "" {
- log.WithFields(log.Fields{
+ log.Debug("Available floating IP found", map[string]string{
"MachineId": d.MachineId,
"IP": ip.Ip,
- }).Debugf("Available floating IP found")
+ })
floatingIP = &ip
break
}
@@ -650,9 +650,9 @@ func (d *Driver) assignFloatingIP() error {
if floatingIP == nil {
floatingIP = &FloatingIP{}
- log.WithField("MachineId", d.MachineId).Debugf("No available floating IP found. Allocating a new one...")
+ log.Debug("No available floating IP found. Allocating a new one...", map[string]string{"MachineId": d.MachineId})
} else {
- log.WithField("MachineId", d.MachineId).Debugf("Assigning floating IP to the instance")
+ log.Debug("Assigning floating IP to the instance", map[string]string{"MachineId": d.MachineId})
}
if err := d.client.AssignFloatingIP(d, floatingIP); err != nil {
@@ -663,7 +663,7 @@ func (d *Driver) assignFloatingIP() error {
}
func (d *Driver) waitForInstanceActive() error {
- log.WithField("MachineId", d.MachineId).Debug("Waiting for the OpenStack instance to be ACTIVE...")
+ log.Debug("Waiting for the OpenStack instance to be ACTIVE...", map[string]string{"MachineId": d.MachineId})
if err := d.client.WaitForInstanceStatus(d, "ACTIVE"); err != nil {
return err
}
@@ -676,10 +676,10 @@ func (d *Driver) lookForIPAddress() error {
return err
}
d.IPAddress = ip
- log.WithFields(log.Fields{
+ log.Debug("IP address found", map[string]string{
"IP": ip,
"MachineId": d.MachineId,
- }).Debug("IP address found")
+ })
return nil
}
diff --git a/drivers/rackspace/client.go b/drivers/rackspace/client.go
index 7a0fa0e2b7..b73263fd06 100644
--- a/drivers/rackspace/client.go
+++ b/drivers/rackspace/client.go
@@ -26,9 +26,9 @@ func (c *Client) Authenticate(d *openstack.Driver) error {
return nil
}
- log.WithFields(log.Fields{
+ log.Debug("Authenticating to Rackspace.", map[string]string{
"Username": d.Username,
- }).Debug("Authenticating to Rackspace.")
+ })
apiKey := c.driver.APIKey
opts := gophercloud.AuthOptions{
diff --git a/drivers/rackspace/rackspace.go b/drivers/rackspace/rackspace.go
index 0fc75f6862..f6e7a0f133 100644
--- a/drivers/rackspace/rackspace.go
+++ b/drivers/rackspace/rackspace.go
@@ -82,9 +82,7 @@ func (d *Driver) GetCreateFlags() []mcnflag.Flag {
// NewDriver instantiates a Rackspace driver.
func NewDriver(machineName, storePath string) drivers.Driver {
- log.WithFields(log.Fields{
- "machineName": machineName,
- }).Debug("Instantiating Rackspace driver.")
+ log.Debug("Instantiating Rackspace driver.", map[string]string{"machineName": machineName})
inner := openstack.NewDerivedDriver(machineName, storePath)
driver := &Driver{
diff --git a/libmachine/drivers/plugin/localbinary/plugin_test.go b/libmachine/drivers/plugin/localbinary/plugin_test.go
index 2ba47b850e..1f38d874e7 100644
--- a/libmachine/drivers/plugin/localbinary/plugin_test.go
+++ b/libmachine/drivers/plugin/localbinary/plugin_test.go
@@ -4,10 +4,11 @@ import (
"bufio"
"fmt"
"io"
- "os"
"testing"
"time"
+ "os"
+
"github.com/docker/machine/libmachine/log"
)
@@ -77,18 +78,16 @@ func TestLocalBinaryPluginClose(t *testing.T) {
}
func TestExecServer(t *testing.T) {
- log.IsDebug = true
+ log.SetDebug(true)
machineName := "test"
logReader, logWriter := io.Pipe()
- log.SetOutWriter(logWriter)
- log.SetErrWriter(logWriter)
+ log.SetOutput(logWriter)
defer func() {
- log.IsDebug = false
- log.SetOutWriter(os.Stdout)
- log.SetErrWriter(os.Stderr)
+ log.SetDebug(false)
+ log.SetOutput(os.Stderr)
}()
stdoutReader, stdoutWriter := io.Pipe()
diff --git a/libmachine/drivers/plugin/register_driver.go b/libmachine/drivers/plugin/register_driver.go
index 7cbb811cdb..268bde5fe3 100644
--- a/libmachine/drivers/plugin/register_driver.go
+++ b/libmachine/drivers/plugin/register_driver.go
@@ -29,7 +29,7 @@ Please use this plugin through the main 'docker-machine' binary.
os.Exit(1)
}
- log.IsDebug = true
+ log.SetDebug(true)
rpcd := rpcdriver.NewRPCServerDriver(d)
rpc.RegisterName(rpcdriver.RPCServiceNameV0, rpcd)
diff --git a/libmachine/examples/vbox_create.go b/libmachine/examples/vbox_create.go
index a2c4e878df..96b67f38b8 100644
--- a/libmachine/examples/vbox_create.go
+++ b/libmachine/examples/vbox_create.go
@@ -4,7 +4,6 @@ package main
import (
"encoding/json"
"fmt"
- "os"
"github.com/docker/machine/drivers/virtualbox"
"github.com/docker/machine/libmachine"
@@ -12,9 +11,7 @@ import (
)
func main() {
- log.IsDebug = true
- log.SetOutWriter(os.Stdout)
- log.SetErrWriter(os.Stderr)
+ log.SetDebug(true)
client := libmachine.NewClient("/tmp/automatic")
diff --git a/libmachine/log/log.go b/libmachine/log/log.go
index 5c0023ddf4..1ef5a552aa 100644
--- a/libmachine/log/log.go
+++ b/libmachine/log/log.go
@@ -1,124 +1,66 @@
package log
-import (
- "io"
- "os"
- "sync"
-)
+import "io"
-// Logger - Why the interface? We may only want to print to STDOUT and STDERR for now,
-// but it won't neccessarily be that way forever. This interface is intended
-// to provide a "framework" for a variety of different logging types in the
-// future (log to file, log to logstash, etc.) There could be a driver model
-// similar to what is done with OS or machine providers.
-type Logger interface {
- Debug(...interface{})
- Debugf(string, ...interface{})
-
- Error(...interface{})
- Errorf(string, ...interface{})
-
- Info(...interface{})
- Infof(string, ...interface{})
-
- Fatal(...interface{})
- Fatalf(string, ...interface{})
-
- Print(...interface{})
- Printf(string, ...interface{})
-
- Warn(...interface{})
- Warnf(string, ...interface{})
-
- WithFields(Fields) Logger
-}
-
-var (
- l = StandardLogger{
- mu: &sync.Mutex{},
- }
- IsDebug = false
-)
-
-type Fields map[string]interface{}
+var logger MachineLogger
func init() {
- // TODO: Is this really the best approach? I worry that it will create
- // implicit behavior which may be problmatic for users of the lib.
- SetOutWriter(os.Stdout)
- SetErrWriter(os.Stderr)
+ logger = NewMachineLogger()
}
-func SetOutWriter(w io.Writer) {
- l.OutWriter = w
-}
-
-func SetErrWriter(w io.Writer) {
- l.ErrWriter = w
+// RedirectStdOutToStdErr prevents any log from corrupting the output
+func RedirectStdOutToStdErr() {
+ logger.RedirectStdOutToStdErr()
}
func Debug(args ...interface{}) {
- l.Debug(args...)
+ logger.Debug(args...)
}
func Debugf(fmtString string, args ...interface{}) {
- l.Debugf(fmtString, args...)
+ logger.Debugf(fmtString, args...)
}
func Error(args ...interface{}) {
- l.Error(args...)
+ logger.Error(args...)
}
func Errorf(fmtString string, args ...interface{}) {
- l.Errorf(fmtString, args...)
-}
-
-func Errorln(args ...interface{}) {
- l.Errorln(args...)
+ logger.Errorf(fmtString, args...)
}
func Info(args ...interface{}) {
- l.Info(args...)
+ logger.Info(args...)
}
func Infof(fmtString string, args ...interface{}) {
- l.Infof(fmtString, args...)
-}
-
-func Infoln(args ...interface{}) {
- l.Infoln(args...)
+ logger.Infof(fmtString, args...)
}
func Fatal(args ...interface{}) {
- l.Fatal(args...)
+ logger.Fatal(args...)
}
func Fatalf(fmtString string, args ...interface{}) {
- l.Fatalf(fmtString, args...)
-}
-
-func Print(args ...interface{}) {
- l.Print(args...)
-}
-
-func Printf(fmtString string, args ...interface{}) {
- l.Printf(fmtString, args...)
+ logger.Fatalf(fmtString, args...)
}
func Warn(args ...interface{}) {
- l.Warn(args...)
+ logger.Warn(args...)
}
func Warnf(fmtString string, args ...interface{}) {
- l.Warnf(fmtString, args...)
+ logger.Warnf(fmtString, args...)
}
-func WithField(fieldName string, field interface{}) Logger {
- return l.WithFields(Fields{
- fieldName: field,
- })
+func Logger() interface{} {
+ return logger
}
-func WithFields(fields Fields) Logger {
- return l.WithFields(fields)
+func SetDebug(debug bool) {
+ logger.SetDebug(debug)
+}
+
+func SetOutput(out io.Writer) {
+ logger.SetOutput(out)
}
diff --git a/libmachine/log/log_test.go b/libmachine/log/log_test.go
index 84c6cab8ea..cb7505bb84 100644
--- a/libmachine/log/log_test.go
+++ b/libmachine/log/log_test.go
@@ -1,19 +1,9 @@
package log
-import "testing"
+import (
+ "testing"
+)
+
+func TestFormatterIsSet(t *testing.T) {
-func TestStandardLoggerWithFields(t *testing.T) {
- logger := StandardLogger{}
- withFieldsLogger := logger.WithFields(Fields{
- "foo": "bar",
- "spam": "eggs",
- })
- withFieldsStandardLogger, ok := withFieldsLogger.(StandardLogger)
- if !ok {
- t.Fatal("Type assertion to StandardLogger failed")
- }
- expectedOutFields := "\t\t foo=bar spam=eggs"
- if withFieldsStandardLogger.fieldOut != expectedOutFields {
- t.Fatalf("Expected %q, got %q", expectedOutFields, withFieldsStandardLogger.fieldOut)
- }
}
diff --git a/libmachine/log/logrus_machine_logger.go b/libmachine/log/logrus_machine_logger.go
new file mode 100644
index 0000000000..df2d45bc96
--- /dev/null
+++ b/libmachine/log/logrus_machine_logger.go
@@ -0,0 +1,79 @@
+package log
+
+import (
+ "io"
+
+ "github.com/Sirupsen/logrus"
+)
+
+type LogrusMachineLogger struct {
+ logger *logrus.Logger
+}
+
+func NewMachineLogger() MachineLogger {
+ logrusLogger := logrus.New()
+ logrusLogger.Level = logrus.InfoLevel
+ logrusLogger.Formatter = new(MachineFormatter)
+ return LogrusMachineLogger{logrusLogger}
+}
+
+// RedirectStdOutToStdErr prevents any log from corrupting the output
+func (ml LogrusMachineLogger) RedirectStdOutToStdErr() {
+ ml.logger.Level = logrus.ErrorLevel
+}
+
+func (ml LogrusMachineLogger) SetDebug(debug bool) {
+ if debug {
+ ml.logger.Level = logrus.DebugLevel
+ } else {
+ ml.logger.Level = logrus.InfoLevel
+ }
+}
+
+func (ml LogrusMachineLogger) SetOutput(out io.Writer) {
+ ml.logger.Out = out
+}
+
+func (ml LogrusMachineLogger) Logger() interface{} {
+ return ml.logger
+}
+
+func (ml LogrusMachineLogger) Debug(args ...interface{}) {
+ ml.logger.Debug(args...)
+}
+
+func (ml LogrusMachineLogger) Debugf(fmtString string, args ...interface{}) {
+ ml.logger.Debugf(fmtString, args...)
+}
+
+func (ml LogrusMachineLogger) Error(args ...interface{}) {
+ ml.logger.Error(args...)
+}
+
+func (ml LogrusMachineLogger) Errorf(fmtString string, args ...interface{}) {
+ ml.logger.Errorf(fmtString, args...)
+}
+
+func (ml LogrusMachineLogger) Info(args ...interface{}) {
+ ml.logger.Info(args...)
+}
+
+func (ml LogrusMachineLogger) Infof(fmtString string, args ...interface{}) {
+ ml.logger.Infof(fmtString, args...)
+}
+
+func (ml LogrusMachineLogger) Fatal(args ...interface{}) {
+ ml.logger.Fatal(args...)
+}
+
+func (ml LogrusMachineLogger) Fatalf(fmtString string, args ...interface{}) {
+ ml.logger.Fatalf(fmtString, args...)
+}
+
+func (ml LogrusMachineLogger) Warn(args ...interface{}) {
+ ml.logger.Warn(args...)
+}
+
+func (ml LogrusMachineLogger) Warnf(fmtString string, args ...interface{}) {
+ ml.logger.Warnf(fmtString, args...)
+}
diff --git a/libmachine/log/logrus_machine_logger_test.go b/libmachine/log/logrus_machine_logger_test.go
new file mode 100644
index 0000000000..7bbe4dd4fc
--- /dev/null
+++ b/libmachine/log/logrus_machine_logger_test.go
@@ -0,0 +1,77 @@
+package log
+
+import (
+ "testing"
+
+ "bufio"
+ "io"
+
+ "github.com/Sirupsen/logrus"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestDefaultLevelIsInfo(t *testing.T) {
+ testLogger := NewMachineLogger()
+ assert.Equal(t, testLogger.Logger().(*logrus.Logger).Level, logrus.InfoLevel)
+}
+
+func TestSetDebugToTrue(t *testing.T) {
+ testLogger := NewMachineLogger()
+ testLogger.SetDebug(true)
+ assert.Equal(t, testLogger.Logger().(*logrus.Logger).Level, logrus.DebugLevel)
+}
+
+func TestSetDebugToFalse(t *testing.T) {
+ testLogger := NewMachineLogger()
+ testLogger.SetDebug(true)
+ testLogger.SetDebug(false)
+ assert.Equal(t, testLogger.Logger().(*logrus.Logger).Level, logrus.InfoLevel)
+}
+
+func TestSetSilenceOutput(t *testing.T) {
+ testLogger := NewMachineLogger()
+ testLogger.RedirectStdOutToStdErr()
+ assert.Equal(t, testLogger.Logger().(*logrus.Logger).Level, logrus.ErrorLevel)
+}
+
+func TestDebug(t *testing.T) {
+ testLogger := NewMachineLogger()
+ testLogger.SetDebug(true)
+
+ result := captureOutput(testLogger, func() { testLogger.Debug("debug") })
+
+ assert.Equal(t, result, "debug")
+}
+
+func TestInfo(t *testing.T) {
+ testLogger := NewMachineLogger()
+
+ result := captureOutput(testLogger, func() { testLogger.Info("info") })
+
+ assert.Equal(t, result, "info")
+}
+
+func TestWarn(t *testing.T) {
+ testLogger := NewMachineLogger()
+
+ result := captureOutput(testLogger, func() { testLogger.Warn("warn") })
+
+ assert.Equal(t, result, "warn")
+}
+
+func TestError(t *testing.T) {
+ testLogger := NewMachineLogger()
+
+ result := captureOutput(testLogger, func() { testLogger.Error("error") })
+
+ assert.Equal(t, result, "error")
+}
+
+func captureOutput(testLogger MachineLogger, lambda func()) string {
+ pipeReader, pipeWriter := io.Pipe()
+ scanner := bufio.NewScanner(pipeReader)
+ testLogger.SetOutput(pipeWriter)
+ go lambda()
+ scanner.Scan()
+ return scanner.Text()
+}
diff --git a/libmachine/log/machine_formatter.go b/libmachine/log/machine_formatter.go
new file mode 100644
index 0000000000..e159918bb4
--- /dev/null
+++ b/libmachine/log/machine_formatter.go
@@ -0,0 +1,19 @@
+package log
+
+import (
+ "bytes"
+
+ "github.com/Sirupsen/logrus"
+)
+
+type MachineFormatter struct {
+}
+
+func (d *MachineFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ b := &bytes.Buffer{}
+
+ b.WriteString(entry.Message)
+ b.WriteByte('\n')
+
+ return b.Bytes(), nil
+}
diff --git a/libmachine/log/machine_formatter_test.go b/libmachine/log/machine_formatter_test.go
new file mode 100644
index 0000000000..357f4068b1
--- /dev/null
+++ b/libmachine/log/machine_formatter_test.go
@@ -0,0 +1,17 @@
+package log
+
+import (
+ "testing"
+
+ "github.com/Sirupsen/logrus"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestWrite(t *testing.T) {
+ entry := logrus.NewEntry(logrus.New())
+ entry.Message = "foobar"
+ formatter := MachineFormatter{}
+ bytes, err := formatter.Format(entry)
+ assert.Nil(t, err)
+ assert.Equal(t, string(bytes[:]), "foobar\n")
+}
diff --git a/libmachine/log/machine_logger.go b/libmachine/log/machine_logger.go
new file mode 100644
index 0000000000..4ef5adb922
--- /dev/null
+++ b/libmachine/log/machine_logger.go
@@ -0,0 +1,28 @@
+package log
+
+import "io"
+
+type MachineLogger interface {
+ RedirectStdOutToStdErr()
+
+ SetDebug(debug bool)
+
+ SetOutput(io.Writer)
+
+ Debug(args ...interface{})
+ Debugf(fmtString string, args ...interface{})
+
+ Error(args ...interface{})
+ Errorf(fmtString string, args ...interface{})
+
+ Info(args ...interface{})
+ Infof(fmtString string, args ...interface{})
+
+ Fatal(args ...interface{})
+ Fatalf(fmtString string, args ...interface{})
+
+ Warn(args ...interface{})
+ Warnf(fmtString string, args ...interface{})
+
+ Logger() interface{}
+}
diff --git a/libmachine/log/standard.go b/libmachine/log/standard.go
deleted file mode 100644
index 807a8cf42c..0000000000
--- a/libmachine/log/standard.go
+++ /dev/null
@@ -1,144 +0,0 @@
-package log
-
-import (
- "fmt"
- "io"
- "os"
- "sort"
- "sync"
-)
-
-type StandardLogger struct {
- // fieldOut is used to do log.WithFields correctly
- fieldOut string
- OutWriter io.Writer
- ErrWriter io.Writer
- mu *sync.Mutex
-}
-
-func (t StandardLogger) log(args ...interface{}) {
- defer t.mu.Unlock()
- t.mu.Lock()
- fmt.Fprint(t.OutWriter, args...)
- fmt.Fprint(t.OutWriter, t.fieldOut, "\n")
- t.fieldOut = ""
-}
-
-func (t StandardLogger) logf(fmtString string, args ...interface{}) {
- defer t.mu.Unlock()
- t.mu.Lock()
- fmt.Fprintf(t.OutWriter, fmtString, args...)
- fmt.Fprint(t.OutWriter, "\n")
- t.fieldOut = ""
-}
-
-func (t StandardLogger) err(args ...interface{}) {
- defer t.mu.Unlock()
- t.mu.Lock()
- fmt.Fprint(t.ErrWriter, args...)
- fmt.Fprint(t.ErrWriter, t.fieldOut, "\n")
- t.fieldOut = ""
-}
-
-func (t StandardLogger) errf(fmtString string, args ...interface{}) {
- defer t.mu.Unlock()
- t.mu.Lock()
- fmt.Fprintf(t.ErrWriter, fmtString, args...)
- fmt.Fprint(t.ErrWriter, t.fieldOut, "\n")
- t.fieldOut = ""
-}
-
-func (t StandardLogger) Debug(args ...interface{}) {
- if IsDebug {
- t.err(args...)
- }
-}
-
-func (t StandardLogger) Debugf(fmtString string, args ...interface{}) {
- if IsDebug {
- t.errf(fmtString, args...)
- }
-}
-
-func (t StandardLogger) Error(args ...interface{}) {
- t.err(args...)
-}
-
-func (t StandardLogger) Errorf(fmtString string, args ...interface{}) {
- t.errf(fmtString, args...)
-}
-
-func (t StandardLogger) Errorln(args ...interface{}) {
- t.err(args...)
-}
-
-func (t StandardLogger) Info(args ...interface{}) {
- t.log(args...)
-}
-
-func (t StandardLogger) Infof(fmtString string, args ...interface{}) {
- t.logf(fmtString, args...)
-}
-
-func (t StandardLogger) Infoln(args ...interface{}) {
- t.log(args...)
-}
-
-func (t StandardLogger) Fatal(args ...interface{}) {
- t.err(args...)
- os.Exit(1)
-}
-
-func (t StandardLogger) Fatalf(fmtString string, args ...interface{}) {
- t.errf(fmtString, args...)
- os.Exit(1)
-}
-
-func (t StandardLogger) Print(args ...interface{}) {
- t.log(args...)
-}
-
-func (t StandardLogger) Printf(fmtString string, args ...interface{}) {
- t.logf(fmtString, args...)
-}
-
-func (t StandardLogger) Warn(args ...interface{}) {
- fmt.Print("WARNING >>> ")
- t.log(args...)
-}
-
-func (t StandardLogger) Warnf(fmtString string, args ...interface{}) {
- fmt.Print("WARNING >>> ")
- t.logf(fmtString, args...)
-}
-
-func (t StandardLogger) WithFields(fields Fields) Logger {
- // When the user calls WithFields, we make a string which gets appended
- // to the output of the final [Info|Warn|Error] call for the
- // descriptive fields. Because WithFields returns the proper Logger
- // (with the fieldOut string set correctly), the logrus syntax will
- // still work.
- kvpairs := []string{}
-
- // Why the string slice song and dance? Because Go's map iteration
- // order is random, we will get inconsistent results if we don't sort
- // the fields (or their resulting string K/V pairs, like we have here).
- // Otherwise, we couldn't test this reliably.
- for k, v := range fields {
- kvpairs = append(kvpairs, fmt.Sprintf("%s=%v", k, v))
- }
-
- sort.Strings(kvpairs)
-
- // TODO:
- // 1. Is this thread-safe?
- // 2. Add more tabs?
- t.fieldOut = "\t\t"
-
- for _, s := range kvpairs {
- // TODO: Is %v the correct format string here?
- t.fieldOut = fmt.Sprintf("%s %s", t.fieldOut, s)
- }
-
- return t
-}
diff --git a/vendor/github.com/Sirupsen/logrus/.gitignore b/vendor/github.com/Sirupsen/logrus/.gitignore
new file mode 100644
index 0000000000..66be63a005
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/.gitignore
@@ -0,0 +1 @@
+logrus
diff --git a/vendor/github.com/Sirupsen/logrus/.travis.yml b/vendor/github.com/Sirupsen/logrus/.travis.yml
new file mode 100644
index 0000000000..ec6411425d
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/.travis.yml
@@ -0,0 +1,7 @@
+language: go
+go:
+ - 1.3
+ - 1.4
+ - tip
+install:
+ - go get -t ./...
diff --git a/vendor/github.com/Sirupsen/logrus/CHANGELOG.md b/vendor/github.com/Sirupsen/logrus/CHANGELOG.md
new file mode 100644
index 0000000000..ecc843272b
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/CHANGELOG.md
@@ -0,0 +1,55 @@
+# 0.9.0 (Unreleased)
+
+* logrus/text_formatter: don't emit empty msg
+* logrus/hooks/airbrake: move out of main repository
+* logrus/hooks/sentry: move out of main repository
+* logrus/hooks/papertrail: move out of main repository
+* logrus/hooks/bugsnag: move out of main repository
+
+# 0.8.7
+
+* logrus/core: fix possible race (#216)
+* logrus/doc: small typo fixes and doc improvements
+
+
+# 0.8.6
+
+* hooks/raven: allow passing an initialized client
+
+# 0.8.5
+
+* logrus/core: revert #208
+
+# 0.8.4
+
+* formatter/text: fix data race (#218)
+
+# 0.8.3
+
+* logrus/core: fix entry log level (#208)
+* logrus/core: improve performance of text formatter by 40%
+* logrus/core: expose `LevelHooks` type
+* logrus/core: add support for DragonflyBSD and NetBSD
+* formatter/text: print structs more verbosely
+
+# 0.8.2
+
+* logrus: fix more Fatal family functions
+
+# 0.8.1
+
+* logrus: fix not exiting on `Fatalf` and `Fatalln`
+
+# 0.8.0
+
+* logrus: defaults to stderr instead of stdout
+* hooks/sentry: add special field for `*http.Request`
+* formatter/text: ignore Windows for colors
+
+# 0.7.3
+
+* formatter/\*: allow configuration of timestamp layout
+
+# 0.7.2
+
+* formatter/text: Add configuration option for time format (#158)
diff --git a/vendor/github.com/Sirupsen/logrus/LICENSE b/vendor/github.com/Sirupsen/logrus/LICENSE
new file mode 100644
index 0000000000..f090cb42f3
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Simon Eskildsen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/github.com/Sirupsen/logrus/README.md b/vendor/github.com/Sirupsen/logrus/README.md
new file mode 100644
index 0000000000..ddfe7959dc
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/README.md
@@ -0,0 +1,365 @@
+# Logrus
[](https://travis-ci.org/Sirupsen/logrus) [][godoc]
+
+Logrus is a structured logger for Go (golang), completely API compatible with
+the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
+yet stable (pre 1.0). Logrus itself is completely stable and has been used in
+many large deployments. The core API is unlikely to change much but please
+version control your Logrus to make sure you aren't fetching latest `master` on
+every build.**
+
+Nicely color-coded in development (when a TTY is attached, otherwise just
+plain text):
+
+
+
+With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash
+or Splunk:
+
+```json
+{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
+ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
+
+{"level":"warning","msg":"The group's number increased tremendously!",
+"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
+
+{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
+"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
+
+{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
+"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
+
+{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
+"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
+```
+
+With the default `log.Formatter = new(&log.TextFormatter{})` when a TTY is not
+attached, the output is compatible with the
+[logfmt](http://godoc.org/github.com/kr/logfmt) format:
+
+```text
+time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
+time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
+time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
+time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
+time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
+time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
+exit status 1
+```
+
+#### Example
+
+The simplest way to use Logrus is simply the package-level exported logger:
+
+```go
+package main
+
+import (
+ log "github.com/Sirupsen/logrus"
+)
+
+func main() {
+ log.WithFields(log.Fields{
+ "animal": "walrus",
+ }).Info("A walrus appears")
+}
+```
+
+Note that it's completely api-compatible with the stdlib logger, so you can
+replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"`
+and you'll now have the flexibility of Logrus. You can customize it all you
+want:
+
+```go
+package main
+
+import (
+ "os"
+ log "github.com/Sirupsen/logrus"
+)
+
+func init() {
+ // Log as JSON instead of the default ASCII formatter.
+ log.SetFormatter(&log.JSONFormatter{})
+
+ // Output to stderr instead of stdout, could also be a file.
+ log.SetOutput(os.Stderr)
+
+ // Only log the warning severity or above.
+ log.SetLevel(log.WarnLevel)
+}
+
+func main() {
+ log.WithFields(log.Fields{
+ "animal": "walrus",
+ "size": 10,
+ }).Info("A group of walrus emerges from the ocean")
+
+ log.WithFields(log.Fields{
+ "omg": true,
+ "number": 122,
+ }).Warn("The group's number increased tremendously!")
+
+ log.WithFields(log.Fields{
+ "omg": true,
+ "number": 100,
+ }).Fatal("The ice breaks!")
+
+ // A common pattern is to re-use fields between logging statements by re-using
+ // the logrus.Entry returned from WithFields()
+ contextLogger := log.WithFields(log.Fields{
+ "common": "this is a common field",
+ "other": "I also should be logged always",
+ })
+
+ contextLogger.Info("I'll be logged with common and other field")
+ contextLogger.Info("Me too")
+}
+```
+
+For more advanced usage such as logging to multiple locations from the same
+application, you can also create an instance of the `logrus` Logger:
+
+```go
+package main
+
+import (
+ "github.com/Sirupsen/logrus"
+)
+
+// Create a new instance of the logger. You can have any number of instances.
+var log = logrus.New()
+
+func main() {
+ // The API for setting attributes is a little different than the package level
+ // exported logger. See Godoc.
+ log.Out = os.Stderr
+
+ log.WithFields(logrus.Fields{
+ "animal": "walrus",
+ "size": 10,
+ }).Info("A group of walrus emerges from the ocean")
+}
+```
+
+#### Fields
+
+Logrus encourages careful, structured logging though logging fields instead of
+long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
+to send event %s to topic %s with key %d")`, you should log the much more
+discoverable:
+
+```go
+log.WithFields(log.Fields{
+ "event": event,
+ "topic": topic,
+ "key": key,
+}).Fatal("Failed to send event")
+```
+
+We've found this API forces you to think about logging in a way that produces
+much more useful logging messages. We've been in countless situations where just
+a single added field to a log statement that was already there would've saved us
+hours. The `WithFields` call is optional.
+
+In general, with Logrus using any of the `printf`-family functions should be
+seen as a hint you should add a field, however, you can still use the
+`printf`-family functions with Logrus.
+
+#### Hooks
+
+You can add hooks for logging levels. For example to send errors to an exception
+tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
+multiple places simultaneously, e.g. syslog.
+
+Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
+`init`:
+
+```go
+import (
+ log "github.com/Sirupsen/logrus"
+ "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake"
+ logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
+ "log/syslog"
+)
+
+func init() {
+
+ // Use the Airbrake hook to report errors that have Error severity or above to
+ // an exception tracker. You can create custom hooks, see the Hooks section.
+ log.AddHook(airbrake.NewHook(123, "xyz", "production"))
+
+ hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
+ if err != nil {
+ log.Error("Unable to connect to local syslog daemon")
+ } else {
+ log.AddHook(hook)
+ }
+}
+```
+Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
+
+| Hook | Description |
+| ----- | ----------- |
+| [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. |
+| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
+| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. |
+| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
+| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
+| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. |
+| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
+| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
+| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
+| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
+| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) |
+| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) |
+| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
+| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger |
+| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail |
+| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar |
+| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd |
+| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
+| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
+| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
+| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
+
+#### Level logging
+
+Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
+
+```go
+log.Debug("Useful debugging information.")
+log.Info("Something noteworthy happened!")
+log.Warn("You should probably take a look at this.")
+log.Error("Something failed but I'm not quitting.")
+// Calls os.Exit(1) after logging
+log.Fatal("Bye.")
+// Calls panic() after logging
+log.Panic("I'm bailing.")
+```
+
+You can set the logging level on a `Logger`, then it will only log entries with
+that severity or anything above it:
+
+```go
+// Will log anything that is info or above (warn, error, fatal, panic). Default.
+log.SetLevel(log.InfoLevel)
+```
+
+It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
+environment if your application has that.
+
+#### Entries
+
+Besides the fields added with `WithField` or `WithFields` some fields are
+automatically added to all logging events:
+
+1. `time`. The timestamp when the entry was created.
+2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
+ the `AddFields` call. E.g. `Failed to send event.`
+3. `level`. The logging level. E.g. `info`.
+
+#### Environments
+
+Logrus has no notion of environment.
+
+If you wish for hooks and formatters to only be used in specific environments,
+you should handle that yourself. For example, if your application has a global
+variable `Environment`, which is a string representation of the environment you
+could do:
+
+```go
+import (
+ log "github.com/Sirupsen/logrus"
+)
+
+init() {
+ // do something here to set environment depending on an environment variable
+ // or command-line flag
+ if Environment == "production" {
+ log.SetFormatter(&log.JSONFormatter{})
+ } else {
+ // The TextFormatter is default, you don't actually have to do this.
+ log.SetFormatter(&log.TextFormatter{})
+ }
+}
+```
+
+This configuration is how `logrus` was intended to be used, but JSON in
+production is mostly only useful if you do log aggregation with tools like
+Splunk or Logstash.
+
+#### Formatters
+
+The built-in logging formatters are:
+
+* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
+ without colors.
+ * *Note:* to force colored output when there is no TTY, set the `ForceColors`
+ field to `true`. To force no colored output even if there is a TTY set the
+ `DisableColors` field to `true`
+* `logrus.JSONFormatter`. Logs fields as JSON.
+* `logrus/formatters/logstash.LogstashFormatter`. Logs fields as [Logstash](http://logstash.net) Events.
+
+ ```go
+ logrus.SetFormatter(&logstash.LogstashFormatter{Type: “application_name"})
+ ```
+
+Third party logging formatters:
+
+* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
+* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
+
+You can define your formatter by implementing the `Formatter` interface,
+requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
+`Fields` type (`map[string]interface{}`) with all your fields as well as the
+default ones (see Entries section above):
+
+```go
+type MyJSONFormatter struct {
+}
+
+log.SetFormatter(new(MyJSONFormatter))
+
+func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
+ // Note this doesn't include Time, Level and Message which are available on
+ // the Entry. Consult `godoc` on information about those fields or read the
+ // source of the official loggers.
+ serialized, err := json.Marshal(entry.Data)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
+ }
+ return append(serialized, '\n'), nil
+}
+```
+
+#### Logger as an `io.Writer`
+
+Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
+
+```go
+w := logger.Writer()
+defer w.Close()
+
+srv := http.Server{
+ // create a stdlib log.Logger that writes to
+ // logrus.Logger.
+ ErrorLog: log.New(w, "", 0),
+}
+```
+
+Each line written to that writer will be printed the usual way, using formatters
+and hooks. The level for those entries is `info`.
+
+#### Rotation
+
+Log rotation is not provided with Logrus. Log rotation should be done by an
+external program (like `logrotate(8)`) that can compress and delete old log
+entries. It should not be a feature of the application-level logger.
+
+#### Tools
+
+| Tool | Description |
+| ---- | ----------- |
+|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
+
+[godoc]: https://godoc.org/github.com/Sirupsen/logrus
diff --git a/vendor/github.com/Sirupsen/logrus/doc.go b/vendor/github.com/Sirupsen/logrus/doc.go
new file mode 100644
index 0000000000..dddd5f877b
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/doc.go
@@ -0,0 +1,26 @@
+/*
+Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
+
+
+The simplest way to use Logrus is simply the package-level exported logger:
+
+ package main
+
+ import (
+ log "github.com/Sirupsen/logrus"
+ )
+
+ func main() {
+ log.WithFields(log.Fields{
+ "animal": "walrus",
+ "number": 1,
+ "size": 10,
+ }).Info("A walrus appears")
+ }
+
+Output:
+ time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
+
+For a full guide visit https://github.com/Sirupsen/logrus
+*/
+package logrus
diff --git a/vendor/github.com/Sirupsen/logrus/entry.go b/vendor/github.com/Sirupsen/logrus/entry.go
new file mode 100644
index 0000000000..9ae900bc5e
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/entry.go
@@ -0,0 +1,264 @@
+package logrus
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "time"
+)
+
+// Defines the key when adding errors using WithError.
+var ErrorKey = "error"
+
+// An entry is the final or intermediate Logrus logging entry. It contains all
+// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
+// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
+// passed around as much as you wish to avoid field duplication.
+type Entry struct {
+ Logger *Logger
+
+ // Contains all the fields set by the user.
+ Data Fields
+
+ // Time at which the log entry was created
+ Time time.Time
+
+ // Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
+ Level Level
+
+ // Message passed to Debug, Info, Warn, Error, Fatal or Panic
+ Message string
+}
+
+func NewEntry(logger *Logger) *Entry {
+ return &Entry{
+ Logger: logger,
+ // Default is three fields, give a little extra room
+ Data: make(Fields, 5),
+ }
+}
+
+// Returns a reader for the entry, which is a proxy to the formatter.
+func (entry *Entry) Reader() (*bytes.Buffer, error) {
+ serialized, err := entry.Logger.Formatter.Format(entry)
+ return bytes.NewBuffer(serialized), err
+}
+
+// Returns the string representation from the reader and ultimately the
+// formatter.
+func (entry *Entry) String() (string, error) {
+ reader, err := entry.Reader()
+ if err != nil {
+ return "", err
+ }
+
+ return reader.String(), err
+}
+
+// Add an error as single field (using the key defined in ErrorKey) to the Entry.
+func (entry *Entry) WithError(err error) *Entry {
+ return entry.WithField(ErrorKey, err)
+}
+
+// Add a single field to the Entry.
+func (entry *Entry) WithField(key string, value interface{}) *Entry {
+ return entry.WithFields(Fields{key: value})
+}
+
+// Add a map of fields to the Entry.
+func (entry *Entry) WithFields(fields Fields) *Entry {
+ data := Fields{}
+ for k, v := range entry.Data {
+ data[k] = v
+ }
+ for k, v := range fields {
+ data[k] = v
+ }
+ return &Entry{Logger: entry.Logger, Data: data}
+}
+
+// This function is not declared with a pointer value because otherwise
+// race conditions will occur when using multiple goroutines
+func (entry Entry) log(level Level, msg string) {
+ entry.Time = time.Now()
+ entry.Level = level
+ entry.Message = msg
+
+ if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
+ entry.Logger.mu.Lock()
+ fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
+ entry.Logger.mu.Unlock()
+ }
+
+ reader, err := entry.Reader()
+ if err != nil {
+ entry.Logger.mu.Lock()
+ fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
+ entry.Logger.mu.Unlock()
+ }
+
+ entry.Logger.mu.Lock()
+ defer entry.Logger.mu.Unlock()
+
+ _, err = io.Copy(entry.Logger.Out, reader)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
+ }
+
+ // To avoid Entry#log() returning a value that only would make sense for
+ // panic() to use in Entry#Panic(), we avoid the allocation by checking
+ // directly here.
+ if level <= PanicLevel {
+ panic(&entry)
+ }
+}
+
+func (entry *Entry) Debug(args ...interface{}) {
+ if entry.Logger.Level >= DebugLevel {
+ entry.log(DebugLevel, fmt.Sprint(args...))
+ }
+}
+
+func (entry *Entry) Print(args ...interface{}) {
+ entry.Info(args...)
+}
+
+func (entry *Entry) Info(args ...interface{}) {
+ if entry.Logger.Level >= InfoLevel {
+ entry.log(InfoLevel, fmt.Sprint(args...))
+ }
+}
+
+func (entry *Entry) Warn(args ...interface{}) {
+ if entry.Logger.Level >= WarnLevel {
+ entry.log(WarnLevel, fmt.Sprint(args...))
+ }
+}
+
+func (entry *Entry) Warning(args ...interface{}) {
+ entry.Warn(args...)
+}
+
+func (entry *Entry) Error(args ...interface{}) {
+ if entry.Logger.Level >= ErrorLevel {
+ entry.log(ErrorLevel, fmt.Sprint(args...))
+ }
+}
+
+func (entry *Entry) Fatal(args ...interface{}) {
+ if entry.Logger.Level >= FatalLevel {
+ entry.log(FatalLevel, fmt.Sprint(args...))
+ }
+ os.Exit(1)
+}
+
+func (entry *Entry) Panic(args ...interface{}) {
+ if entry.Logger.Level >= PanicLevel {
+ entry.log(PanicLevel, fmt.Sprint(args...))
+ }
+ panic(fmt.Sprint(args...))
+}
+
+// Entry Printf family functions
+
+func (entry *Entry) Debugf(format string, args ...interface{}) {
+ if entry.Logger.Level >= DebugLevel {
+ entry.Debug(fmt.Sprintf(format, args...))
+ }
+}
+
+func (entry *Entry) Infof(format string, args ...interface{}) {
+ if entry.Logger.Level >= InfoLevel {
+ entry.Info(fmt.Sprintf(format, args...))
+ }
+}
+
+func (entry *Entry) Printf(format string, args ...interface{}) {
+ entry.Infof(format, args...)
+}
+
+func (entry *Entry) Warnf(format string, args ...interface{}) {
+ if entry.Logger.Level >= WarnLevel {
+ entry.Warn(fmt.Sprintf(format, args...))
+ }
+}
+
+func (entry *Entry) Warningf(format string, args ...interface{}) {
+ entry.Warnf(format, args...)
+}
+
+func (entry *Entry) Errorf(format string, args ...interface{}) {
+ if entry.Logger.Level >= ErrorLevel {
+ entry.Error(fmt.Sprintf(format, args...))
+ }
+}
+
+func (entry *Entry) Fatalf(format string, args ...interface{}) {
+ if entry.Logger.Level >= FatalLevel {
+ entry.Fatal(fmt.Sprintf(format, args...))
+ }
+ os.Exit(1)
+}
+
+func (entry *Entry) Panicf(format string, args ...interface{}) {
+ if entry.Logger.Level >= PanicLevel {
+ entry.Panic(fmt.Sprintf(format, args...))
+ }
+}
+
+// Entry Println family functions
+
+func (entry *Entry) Debugln(args ...interface{}) {
+ if entry.Logger.Level >= DebugLevel {
+ entry.Debug(entry.sprintlnn(args...))
+ }
+}
+
+func (entry *Entry) Infoln(args ...interface{}) {
+ if entry.Logger.Level >= InfoLevel {
+ entry.Info(entry.sprintlnn(args...))
+ }
+}
+
+func (entry *Entry) Println(args ...interface{}) {
+ entry.Infoln(args...)
+}
+
+func (entry *Entry) Warnln(args ...interface{}) {
+ if entry.Logger.Level >= WarnLevel {
+ entry.Warn(entry.sprintlnn(args...))
+ }
+}
+
+func (entry *Entry) Warningln(args ...interface{}) {
+ entry.Warnln(args...)
+}
+
+func (entry *Entry) Errorln(args ...interface{}) {
+ if entry.Logger.Level >= ErrorLevel {
+ entry.Error(entry.sprintlnn(args...))
+ }
+}
+
+func (entry *Entry) Fatalln(args ...interface{}) {
+ if entry.Logger.Level >= FatalLevel {
+ entry.Fatal(entry.sprintlnn(args...))
+ }
+ os.Exit(1)
+}
+
+func (entry *Entry) Panicln(args ...interface{}) {
+ if entry.Logger.Level >= PanicLevel {
+ entry.Panic(entry.sprintlnn(args...))
+ }
+}
+
+// Sprintlnn => Sprint no newline. This is to get the behavior of how
+// fmt.Sprintln where spaces are always added between operands, regardless of
+// their type. Instead of vendoring the Sprintln implementation to spare a
+// string allocation, we do the simplest thing.
+func (entry *Entry) sprintlnn(args ...interface{}) string {
+ msg := fmt.Sprintln(args...)
+ return msg[:len(msg)-1]
+}
diff --git a/vendor/github.com/Sirupsen/logrus/examples/basic/basic.go b/vendor/github.com/Sirupsen/logrus/examples/basic/basic.go
new file mode 100644
index 0000000000..a1623ec003
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/examples/basic/basic.go
@@ -0,0 +1,50 @@
+package main
+
+import (
+ "github.com/Sirupsen/logrus"
+)
+
+var log = logrus.New()
+
+func init() {
+ log.Formatter = new(logrus.JSONFormatter)
+ log.Formatter = new(logrus.TextFormatter) // default
+ log.Level = logrus.DebugLevel
+}
+
+func main() {
+ defer func() {
+ err := recover()
+ if err != nil {
+ log.WithFields(logrus.Fields{
+ "omg": true,
+ "err": err,
+ "number": 100,
+ }).Fatal("The ice breaks!")
+ }
+ }()
+
+ log.WithFields(logrus.Fields{
+ "animal": "walrus",
+ "number": 8,
+ }).Debug("Started observing beach")
+
+ log.WithFields(logrus.Fields{
+ "animal": "walrus",
+ "size": 10,
+ }).Info("A group of walrus emerges from the ocean")
+
+ log.WithFields(logrus.Fields{
+ "omg": true,
+ "number": 122,
+ }).Warn("The group's number increased tremendously!")
+
+ log.WithFields(logrus.Fields{
+ "temperature": -4,
+ }).Debug("Temperature changes")
+
+ log.WithFields(logrus.Fields{
+ "animal": "orca",
+ "size": 9009,
+ }).Panic("It's over 9000!")
+}
diff --git a/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go b/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go
new file mode 100644
index 0000000000..3187f6d3e1
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go
@@ -0,0 +1,30 @@
+package main
+
+import (
+ "github.com/Sirupsen/logrus"
+ "gopkg.in/gemnasium/logrus-airbrake-hook.v2"
+)
+
+var log = logrus.New()
+
+func init() {
+ log.Formatter = new(logrus.TextFormatter) // default
+ log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
+}
+
+func main() {
+ log.WithFields(logrus.Fields{
+ "animal": "walrus",
+ "size": 10,
+ }).Info("A group of walrus emerges from the ocean")
+
+ log.WithFields(logrus.Fields{
+ "omg": true,
+ "number": 122,
+ }).Warn("The group's number increased tremendously!")
+
+ log.WithFields(logrus.Fields{
+ "omg": true,
+ "number": 100,
+ }).Fatal("The ice breaks!")
+}
diff --git a/vendor/github.com/Sirupsen/logrus/exported.go b/vendor/github.com/Sirupsen/logrus/exported.go
new file mode 100644
index 0000000000..9a0120ac1d
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/exported.go
@@ -0,0 +1,193 @@
+package logrus
+
+import (
+ "io"
+)
+
+var (
+ // std is the name of the standard logger in stdlib `log`
+ std = New()
+)
+
+func StandardLogger() *Logger {
+ return std
+}
+
+// SetOutput sets the standard logger output.
+func SetOutput(out io.Writer) {
+ std.mu.Lock()
+ defer std.mu.Unlock()
+ std.Out = out
+}
+
+// SetFormatter sets the standard logger formatter.
+func SetFormatter(formatter Formatter) {
+ std.mu.Lock()
+ defer std.mu.Unlock()
+ std.Formatter = formatter
+}
+
+// SetLevel sets the standard logger level.
+func SetLevel(level Level) {
+ std.mu.Lock()
+ defer std.mu.Unlock()
+ std.Level = level
+}
+
+// GetLevel returns the standard logger level.
+func GetLevel() Level {
+ std.mu.Lock()
+ defer std.mu.Unlock()
+ return std.Level
+}
+
+// AddHook adds a hook to the standard logger hooks.
+func AddHook(hook Hook) {
+ std.mu.Lock()
+ defer std.mu.Unlock()
+ std.Hooks.Add(hook)
+}
+
+// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
+func WithError(err error) *Entry {
+ return std.WithField(ErrorKey, err)
+}
+
+// WithField creates an entry from the standard logger and adds a field to
+// it. If you want multiple fields, use `WithFields`.
+//
+// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
+// or Panic on the Entry it returns.
+func WithField(key string, value interface{}) *Entry {
+ return std.WithField(key, value)
+}
+
+// WithFields creates an entry from the standard logger and adds multiple
+// fields to it. This is simply a helper for `WithField`, invoking it
+// once for each field.
+//
+// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
+// or Panic on the Entry it returns.
+func WithFields(fields Fields) *Entry {
+ return std.WithFields(fields)
+}
+
+// Debug logs a message at level Debug on the standard logger.
+func Debug(args ...interface{}) {
+ std.Debug(args...)
+}
+
+// Print logs a message at level Info on the standard logger.
+func Print(args ...interface{}) {
+ std.Print(args...)
+}
+
+// Info logs a message at level Info on the standard logger.
+func Info(args ...interface{}) {
+ std.Info(args...)
+}
+
+// Warn logs a message at level Warn on the standard logger.
+func Warn(args ...interface{}) {
+ std.Warn(args...)
+}
+
+// Warning logs a message at level Warn on the standard logger.
+func Warning(args ...interface{}) {
+ std.Warning(args...)
+}
+
+// Error logs a message at level Error on the standard logger.
+func Error(args ...interface{}) {
+ std.Error(args...)
+}
+
+// Panic logs a message at level Panic on the standard logger.
+func Panic(args ...interface{}) {
+ std.Panic(args...)
+}
+
+// Fatal logs a message at level Fatal on the standard logger.
+func Fatal(args ...interface{}) {
+ std.Fatal(args...)
+}
+
+// Debugf logs a message at level Debug on the standard logger.
+func Debugf(format string, args ...interface{}) {
+ std.Debugf(format, args...)
+}
+
+// Printf logs a message at level Info on the standard logger.
+func Printf(format string, args ...interface{}) {
+ std.Printf(format, args...)
+}
+
+// Infof logs a message at level Info on the standard logger.
+func Infof(format string, args ...interface{}) {
+ std.Infof(format, args...)
+}
+
+// Warnf logs a message at level Warn on the standard logger.
+func Warnf(format string, args ...interface{}) {
+ std.Warnf(format, args...)
+}
+
+// Warningf logs a message at level Warn on the standard logger.
+func Warningf(format string, args ...interface{}) {
+ std.Warningf(format, args...)
+}
+
+// Errorf logs a message at level Error on the standard logger.
+func Errorf(format string, args ...interface{}) {
+ std.Errorf(format, args...)
+}
+
+// Panicf logs a message at level Panic on the standard logger.
+func Panicf(format string, args ...interface{}) {
+ std.Panicf(format, args...)
+}
+
+// Fatalf logs a message at level Fatal on the standard logger.
+func Fatalf(format string, args ...interface{}) {
+ std.Fatalf(format, args...)
+}
+
+// Debugln logs a message at level Debug on the standard logger.
+func Debugln(args ...interface{}) {
+ std.Debugln(args...)
+}
+
+// Println logs a message at level Info on the standard logger.
+func Println(args ...interface{}) {
+ std.Println(args...)
+}
+
+// Infoln logs a message at level Info on the standard logger.
+func Infoln(args ...interface{}) {
+ std.Infoln(args...)
+}
+
+// Warnln logs a message at level Warn on the standard logger.
+func Warnln(args ...interface{}) {
+ std.Warnln(args...)
+}
+
+// Warningln logs a message at level Warn on the standard logger.
+func Warningln(args ...interface{}) {
+ std.Warningln(args...)
+}
+
+// Errorln logs a message at level Error on the standard logger.
+func Errorln(args ...interface{}) {
+ std.Errorln(args...)
+}
+
+// Panicln logs a message at level Panic on the standard logger.
+func Panicln(args ...interface{}) {
+ std.Panicln(args...)
+}
+
+// Fatalln logs a message at level Fatal on the standard logger.
+func Fatalln(args ...interface{}) {
+ std.Fatalln(args...)
+}
diff --git a/vendor/github.com/Sirupsen/logrus/formatter.go b/vendor/github.com/Sirupsen/logrus/formatter.go
new file mode 100644
index 0000000000..104d689f18
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/formatter.go
@@ -0,0 +1,48 @@
+package logrus
+
+import "time"
+
+const DefaultTimestampFormat = time.RFC3339
+
+// The Formatter interface is used to implement a custom Formatter. It takes an
+// `Entry`. It exposes all the fields, including the default ones:
+//
+// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
+// * `entry.Data["time"]`. The timestamp.
+// * `entry.Data["level"]. The level the entry was logged at.
+//
+// Any additional fields added with `WithField` or `WithFields` are also in
+// `entry.Data`. Format is expected to return an array of bytes which are then
+// logged to `logger.Out`.
+type Formatter interface {
+ Format(*Entry) ([]byte, error)
+}
+
+// This is to not silently overwrite `time`, `msg` and `level` fields when
+// dumping it. If this code wasn't there doing:
+//
+// logrus.WithField("level", 1).Info("hello")
+//
+// Would just silently drop the user provided level. Instead with this code
+// it'll logged as:
+//
+// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
+//
+// It's not exported because it's still using Data in an opinionated way. It's to
+// avoid code duplication between the two default formatters.
+func prefixFieldClashes(data Fields) {
+ _, ok := data["time"]
+ if ok {
+ data["fields.time"] = data["time"]
+ }
+
+ _, ok = data["msg"]
+ if ok {
+ data["fields.msg"] = data["msg"]
+ }
+
+ _, ok = data["level"]
+ if ok {
+ data["fields.level"] = data["level"]
+ }
+}
diff --git a/vendor/github.com/Sirupsen/logrus/formatters/logstash/logstash.go b/vendor/github.com/Sirupsen/logrus/formatters/logstash/logstash.go
new file mode 100644
index 0000000000..8ea93ddf20
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/formatters/logstash/logstash.go
@@ -0,0 +1,56 @@
+package logstash
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/Sirupsen/logrus"
+)
+
+// Formatter generates json in logstash format.
+// Logstash site: http://logstash.net/
+type LogstashFormatter struct {
+ Type string // if not empty use for logstash type field.
+
+ // TimestampFormat sets the format used for timestamps.
+ TimestampFormat string
+}
+
+func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ entry.Data["@version"] = 1
+
+ if f.TimestampFormat == "" {
+ f.TimestampFormat = logrus.DefaultTimestampFormat
+ }
+
+ entry.Data["@timestamp"] = entry.Time.Format(f.TimestampFormat)
+
+ // set message field
+ v, ok := entry.Data["message"]
+ if ok {
+ entry.Data["fields.message"] = v
+ }
+ entry.Data["message"] = entry.Message
+
+ // set level field
+ v, ok = entry.Data["level"]
+ if ok {
+ entry.Data["fields.level"] = v
+ }
+ entry.Data["level"] = entry.Level.String()
+
+ // set type field
+ if f.Type != "" {
+ v, ok = entry.Data["type"]
+ if ok {
+ entry.Data["fields.type"] = v
+ }
+ entry.Data["type"] = f.Type
+ }
+
+ serialized, err := json.Marshal(entry.Data)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
+ }
+ return append(serialized, '\n'), nil
+}
diff --git a/vendor/github.com/Sirupsen/logrus/hooks.go b/vendor/github.com/Sirupsen/logrus/hooks.go
new file mode 100644
index 0000000000..3f151cdc39
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/hooks.go
@@ -0,0 +1,34 @@
+package logrus
+
+// A hook to be fired when logging on the logging levels returned from
+// `Levels()` on your implementation of the interface. Note that this is not
+// fired in a goroutine or a channel with workers, you should handle such
+// functionality yourself if your call is non-blocking and you don't wish for
+// the logging calls for levels returned from `Levels()` to block.
+type Hook interface {
+ Levels() []Level
+ Fire(*Entry) error
+}
+
+// Internal type for storing the hooks on a logger instance.
+type LevelHooks map[Level][]Hook
+
+// Add a hook to an instance of logger. This is called with
+// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
+func (hooks LevelHooks) Add(hook Hook) {
+ for _, level := range hook.Levels() {
+ hooks[level] = append(hooks[level], hook)
+ }
+}
+
+// Fire all the hooks for the passed level. Used by `entry.log` to fire
+// appropriate hooks for a log entry.
+func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
+ for _, hook := range hooks[level] {
+ if err := hook.Fire(entry); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
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 0000000000..066704b370
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/hooks/syslog/README.md
@@ -0,0 +1,39 @@
+# Syslog Hooks for Logrus
+
+## 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)
+ }
+}
+```
+
+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 `NewSyslogHook`. It should look like the following.
+
+```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("", "", syslog.LOG_INFO, "")
+
+ if err == nil {
+ log.Hooks.Add(hook)
+ }
+}
+```
\ No newline at end of file
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 0000000000..c59f331d10
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog.go
@@ -0,0 +1,61 @@
+// +build !windows,!nacl,!plan9
+
+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,
+ }
+}
diff --git a/vendor/github.com/Sirupsen/logrus/json_formatter.go b/vendor/github.com/Sirupsen/logrus/json_formatter.go
new file mode 100644
index 0000000000..2ad6dc5cf4
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/json_formatter.go
@@ -0,0 +1,41 @@
+package logrus
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+type JSONFormatter struct {
+ // TimestampFormat sets the format used for marshaling timestamps.
+ TimestampFormat string
+}
+
+func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
+ data := make(Fields, len(entry.Data)+3)
+ for k, v := range entry.Data {
+ switch v := v.(type) {
+ case error:
+ // Otherwise errors are ignored by `encoding/json`
+ // https://github.com/Sirupsen/logrus/issues/137
+ data[k] = v.Error()
+ default:
+ data[k] = v
+ }
+ }
+ prefixFieldClashes(data)
+
+ timestampFormat := f.TimestampFormat
+ if timestampFormat == "" {
+ timestampFormat = DefaultTimestampFormat
+ }
+
+ data["time"] = entry.Time.Format(timestampFormat)
+ data["msg"] = entry.Message
+ data["level"] = entry.Level.String()
+
+ serialized, err := json.Marshal(data)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
+ }
+ return append(serialized, '\n'), nil
+}
diff --git a/vendor/github.com/Sirupsen/logrus/logger.go b/vendor/github.com/Sirupsen/logrus/logger.go
new file mode 100644
index 0000000000..2fdb231761
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/logger.go
@@ -0,0 +1,212 @@
+package logrus
+
+import (
+ "io"
+ "os"
+ "sync"
+)
+
+type Logger struct {
+ // The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
+ // file, or leave it default which is `os.Stderr`. You can also set this to
+ // something more adventorous, such as logging to Kafka.
+ Out io.Writer
+ // Hooks for the logger instance. These allow firing events based on logging
+ // levels and log entries. For example, to send errors to an error tracking
+ // service, log to StatsD or dump the core on fatal errors.
+ Hooks LevelHooks
+ // All log entries pass through the formatter before logged to Out. The
+ // included formatters are `TextFormatter` and `JSONFormatter` for which
+ // TextFormatter is the default. In development (when a TTY is attached) it
+ // logs with colors, but to a file it wouldn't. You can easily implement your
+ // own that implements the `Formatter` interface, see the `README` or included
+ // formatters for examples.
+ Formatter Formatter
+ // The logging level the logger should log at. This is typically (and defaults
+ // to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
+ // logged. `logrus.Debug` is useful in
+ Level Level
+ // Used to sync writing to the log.
+ mu sync.Mutex
+}
+
+// Creates a new logger. Configuration should be set by changing `Formatter`,
+// `Out` and `Hooks` directly on the default logger instance. You can also just
+// instantiate your own:
+//
+// var log = &Logger{
+// Out: os.Stderr,
+// Formatter: new(JSONFormatter),
+// Hooks: make(LevelHooks),
+// Level: logrus.DebugLevel,
+// }
+//
+// It's recommended to make this a global instance called `log`.
+func New() *Logger {
+ return &Logger{
+ Out: os.Stderr,
+ Formatter: new(TextFormatter),
+ Hooks: make(LevelHooks),
+ Level: InfoLevel,
+ }
+}
+
+// Adds a field to the log entry, note that you it doesn't log until you call
+// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
+// If you want multiple fields, use `WithFields`.
+func (logger *Logger) WithField(key string, value interface{}) *Entry {
+ return NewEntry(logger).WithField(key, value)
+}
+
+// Adds a struct of fields to the log entry. All it does is call `WithField` for
+// each `Field`.
+func (logger *Logger) WithFields(fields Fields) *Entry {
+ return NewEntry(logger).WithFields(fields)
+}
+
+// Add an error as single field to the log entry. All it does is call
+// `WithError` for the given `error`.
+func (logger *Logger) WithError(err error) *Entry {
+ return NewEntry(logger).WithError(err)
+}
+
+func (logger *Logger) Debugf(format string, args ...interface{}) {
+ if logger.Level >= DebugLevel {
+ NewEntry(logger).Debugf(format, args...)
+ }
+}
+
+func (logger *Logger) Infof(format string, args ...interface{}) {
+ if logger.Level >= InfoLevel {
+ NewEntry(logger).Infof(format, args...)
+ }
+}
+
+func (logger *Logger) Printf(format string, args ...interface{}) {
+ NewEntry(logger).Printf(format, args...)
+}
+
+func (logger *Logger) Warnf(format string, args ...interface{}) {
+ if logger.Level >= WarnLevel {
+ NewEntry(logger).Warnf(format, args...)
+ }
+}
+
+func (logger *Logger) Warningf(format string, args ...interface{}) {
+ if logger.Level >= WarnLevel {
+ NewEntry(logger).Warnf(format, args...)
+ }
+}
+
+func (logger *Logger) Errorf(format string, args ...interface{}) {
+ if logger.Level >= ErrorLevel {
+ NewEntry(logger).Errorf(format, args...)
+ }
+}
+
+func (logger *Logger) Fatalf(format string, args ...interface{}) {
+ if logger.Level >= FatalLevel {
+ NewEntry(logger).Fatalf(format, args...)
+ }
+ os.Exit(1)
+}
+
+func (logger *Logger) Panicf(format string, args ...interface{}) {
+ if logger.Level >= PanicLevel {
+ NewEntry(logger).Panicf(format, args...)
+ }
+}
+
+func (logger *Logger) Debug(args ...interface{}) {
+ if logger.Level >= DebugLevel {
+ NewEntry(logger).Debug(args...)
+ }
+}
+
+func (logger *Logger) Info(args ...interface{}) {
+ if logger.Level >= InfoLevel {
+ NewEntry(logger).Info(args...)
+ }
+}
+
+func (logger *Logger) Print(args ...interface{}) {
+ NewEntry(logger).Info(args...)
+}
+
+func (logger *Logger) Warn(args ...interface{}) {
+ if logger.Level >= WarnLevel {
+ NewEntry(logger).Warn(args...)
+ }
+}
+
+func (logger *Logger) Warning(args ...interface{}) {
+ if logger.Level >= WarnLevel {
+ NewEntry(logger).Warn(args...)
+ }
+}
+
+func (logger *Logger) Error(args ...interface{}) {
+ if logger.Level >= ErrorLevel {
+ NewEntry(logger).Error(args...)
+ }
+}
+
+func (logger *Logger) Fatal(args ...interface{}) {
+ if logger.Level >= FatalLevel {
+ NewEntry(logger).Fatal(args...)
+ }
+ os.Exit(1)
+}
+
+func (logger *Logger) Panic(args ...interface{}) {
+ if logger.Level >= PanicLevel {
+ NewEntry(logger).Panic(args...)
+ }
+}
+
+func (logger *Logger) Debugln(args ...interface{}) {
+ if logger.Level >= DebugLevel {
+ NewEntry(logger).Debugln(args...)
+ }
+}
+
+func (logger *Logger) Infoln(args ...interface{}) {
+ if logger.Level >= InfoLevel {
+ NewEntry(logger).Infoln(args...)
+ }
+}
+
+func (logger *Logger) Println(args ...interface{}) {
+ NewEntry(logger).Println(args...)
+}
+
+func (logger *Logger) Warnln(args ...interface{}) {
+ if logger.Level >= WarnLevel {
+ NewEntry(logger).Warnln(args...)
+ }
+}
+
+func (logger *Logger) Warningln(args ...interface{}) {
+ if logger.Level >= WarnLevel {
+ NewEntry(logger).Warnln(args...)
+ }
+}
+
+func (logger *Logger) Errorln(args ...interface{}) {
+ if logger.Level >= ErrorLevel {
+ NewEntry(logger).Errorln(args...)
+ }
+}
+
+func (logger *Logger) Fatalln(args ...interface{}) {
+ if logger.Level >= FatalLevel {
+ NewEntry(logger).Fatalln(args...)
+ }
+ os.Exit(1)
+}
+
+func (logger *Logger) Panicln(args ...interface{}) {
+ if logger.Level >= PanicLevel {
+ NewEntry(logger).Panicln(args...)
+ }
+}
diff --git a/vendor/github.com/Sirupsen/logrus/logrus.go b/vendor/github.com/Sirupsen/logrus/logrus.go
new file mode 100644
index 0000000000..0c09fbc264
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/logrus.go
@@ -0,0 +1,98 @@
+package logrus
+
+import (
+ "fmt"
+ "log"
+)
+
+// Fields type, used to pass to `WithFields`.
+type Fields map[string]interface{}
+
+// Level type
+type Level uint8
+
+// Convert the Level to a string. E.g. PanicLevel becomes "panic".
+func (level Level) String() string {
+ switch level {
+ case DebugLevel:
+ return "debug"
+ case InfoLevel:
+ return "info"
+ case WarnLevel:
+ return "warning"
+ case ErrorLevel:
+ return "error"
+ case FatalLevel:
+ return "fatal"
+ case PanicLevel:
+ return "panic"
+ }
+
+ return "unknown"
+}
+
+// ParseLevel takes a string level and returns the Logrus log level constant.
+func ParseLevel(lvl string) (Level, error) {
+ switch lvl {
+ case "panic":
+ return PanicLevel, nil
+ case "fatal":
+ return FatalLevel, nil
+ case "error":
+ return ErrorLevel, nil
+ case "warn", "warning":
+ return WarnLevel, nil
+ case "info":
+ return InfoLevel, nil
+ case "debug":
+ return DebugLevel, nil
+ }
+
+ var l Level
+ return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
+}
+
+// These are the different logging levels. You can set the logging level to log
+// on your instance of logger, obtained with `logrus.New()`.
+const (
+ // PanicLevel level, highest level of severity. Logs and then calls panic with the
+ // message passed to Debug, Info, ...
+ PanicLevel Level = iota
+ // FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
+ // logging level is set to Panic.
+ FatalLevel
+ // ErrorLevel level. Logs. Used for errors that should definitely be noted.
+ // Commonly used for hooks to send errors to an error tracking service.
+ ErrorLevel
+ // WarnLevel level. Non-critical entries that deserve eyes.
+ WarnLevel
+ // InfoLevel level. General operational entries about what's going on inside the
+ // application.
+ InfoLevel
+ // DebugLevel level. Usually only enabled when debugging. Very verbose logging.
+ DebugLevel
+)
+
+// Won't compile if StdLogger can't be realized by a log.Logger
+var (
+ _ StdLogger = &log.Logger{}
+ _ StdLogger = &Entry{}
+ _ StdLogger = &Logger{}
+)
+
+// StdLogger is what your logrus-enabled library should take, that way
+// it'll accept a stdlib logger and a logrus logger. There's no standard
+// interface, this is the closest we get, unfortunately.
+type StdLogger interface {
+ Print(...interface{})
+ Printf(string, ...interface{})
+ Println(...interface{})
+
+ Fatal(...interface{})
+ Fatalf(string, ...interface{})
+ Fatalln(...interface{})
+
+ Panic(...interface{})
+ Panicf(string, ...interface{})
+ Panicln(...interface{})
+}
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_bsd.go b/vendor/github.com/Sirupsen/logrus/terminal_bsd.go
new file mode 100644
index 0000000000..71f8d67a55
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/terminal_bsd.go
@@ -0,0 +1,9 @@
+// +build darwin freebsd openbsd netbsd dragonfly
+
+package logrus
+
+import "syscall"
+
+const ioctlReadTermios = syscall.TIOCGETA
+
+type Termios syscall.Termios
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_linux.go b/vendor/github.com/Sirupsen/logrus/terminal_linux.go
new file mode 100644
index 0000000000..a2c0b40db6
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/terminal_linux.go
@@ -0,0 +1,12 @@
+// Based on ssh/terminal:
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package logrus
+
+import "syscall"
+
+const ioctlReadTermios = syscall.TCGETS
+
+type Termios syscall.Termios
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_notwindows.go b/vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
new file mode 100644
index 0000000000..4bb5376028
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
@@ -0,0 +1,21 @@
+// Based on ssh/terminal:
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build linux darwin freebsd openbsd netbsd dragonfly
+
+package logrus
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+// IsTerminal returns true if the given file descriptor is a terminal.
+func IsTerminal() bool {
+ fd := syscall.Stdout
+ var termios Termios
+ _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
+ return err == 0
+}
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_solaris.go b/vendor/github.com/Sirupsen/logrus/terminal_solaris.go
new file mode 100644
index 0000000000..3e70bf7bf0
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/terminal_solaris.go
@@ -0,0 +1,15 @@
+// +build solaris
+
+package logrus
+
+import (
+ "os"
+
+ "golang.org/x/sys/unix"
+)
+
+// IsTerminal returns true if the given file descriptor is a terminal.
+func IsTerminal() bool {
+ _, err := unix.IoctlGetTermios(int(os.Stdout.Fd()), unix.TCGETA)
+ return err == nil
+}
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_windows.go b/vendor/github.com/Sirupsen/logrus/terminal_windows.go
new file mode 100644
index 0000000000..2e09f6f7e3
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/terminal_windows.go
@@ -0,0 +1,27 @@
+// Based on ssh/terminal:
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build windows
+
+package logrus
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+var kernel32 = syscall.NewLazyDLL("kernel32.dll")
+
+var (
+ procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
+)
+
+// IsTerminal returns true if the given file descriptor is a terminal.
+func IsTerminal() bool {
+ fd := syscall.Stdout
+ var st uint32
+ r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
+ return r != 0 && e == 0
+}
diff --git a/vendor/github.com/Sirupsen/logrus/text_formatter.go b/vendor/github.com/Sirupsen/logrus/text_formatter.go
new file mode 100644
index 0000000000..06ef202337
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/text_formatter.go
@@ -0,0 +1,161 @@
+package logrus
+
+import (
+ "bytes"
+ "fmt"
+ "runtime"
+ "sort"
+ "strings"
+ "time"
+)
+
+const (
+ nocolor = 0
+ red = 31
+ green = 32
+ yellow = 33
+ blue = 34
+ gray = 37
+)
+
+var (
+ baseTimestamp time.Time
+ isTerminal bool
+)
+
+func init() {
+ baseTimestamp = time.Now()
+ isTerminal = IsTerminal()
+}
+
+func miniTS() int {
+ return int(time.Since(baseTimestamp) / time.Second)
+}
+
+type TextFormatter struct {
+ // Set to true to bypass checking for a TTY before outputting colors.
+ ForceColors bool
+
+ // Force disabling colors.
+ DisableColors bool
+
+ // Disable timestamp logging. useful when output is redirected to logging
+ // system that already adds timestamps.
+ DisableTimestamp bool
+
+ // Enable logging the full timestamp when a TTY is attached instead of just
+ // the time passed since beginning of execution.
+ FullTimestamp bool
+
+ // TimestampFormat to use for display when a full timestamp is printed
+ TimestampFormat string
+
+ // The fields are sorted by default for a consistent output. For applications
+ // that log extremely frequently and don't use the JSON formatter this may not
+ // be desired.
+ DisableSorting bool
+}
+
+func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
+ var keys []string = make([]string, 0, len(entry.Data))
+ for k := range entry.Data {
+ keys = append(keys, k)
+ }
+
+ if !f.DisableSorting {
+ sort.Strings(keys)
+ }
+
+ b := &bytes.Buffer{}
+
+ prefixFieldClashes(entry.Data)
+
+ isColorTerminal := isTerminal && (runtime.GOOS != "windows")
+ isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors
+
+ timestampFormat := f.TimestampFormat
+ if timestampFormat == "" {
+ timestampFormat = DefaultTimestampFormat
+ }
+ if isColored {
+ f.printColored(b, entry, keys, timestampFormat)
+ } else {
+ if !f.DisableTimestamp {
+ f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
+ }
+ f.appendKeyValue(b, "level", entry.Level.String())
+ if entry.Message != "" {
+ f.appendKeyValue(b, "msg", entry.Message)
+ }
+ for _, key := range keys {
+ f.appendKeyValue(b, key, entry.Data[key])
+ }
+ }
+
+ b.WriteByte('\n')
+ return b.Bytes(), nil
+}
+
+func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
+ var levelColor int
+ switch entry.Level {
+ case DebugLevel:
+ levelColor = gray
+ case WarnLevel:
+ levelColor = yellow
+ case ErrorLevel, FatalLevel, PanicLevel:
+ levelColor = red
+ default:
+ levelColor = blue
+ }
+
+ levelText := strings.ToUpper(entry.Level.String())[0:4]
+
+ if !f.FullTimestamp {
+ fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
+ } else {
+ fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
+ }
+ for _, k := range keys {
+ v := entry.Data[k]
+ fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%+v", levelColor, k, v)
+ }
+}
+
+func needsQuoting(text string) bool {
+ for _, ch := range text {
+ if !((ch >= 'a' && ch <= 'z') ||
+ (ch >= 'A' && ch <= 'Z') ||
+ (ch >= '0' && ch <= '9') ||
+ ch == '-' || ch == '.') {
+ return false
+ }
+ }
+ return true
+}
+
+func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
+
+ b.WriteString(key)
+ b.WriteByte('=')
+
+ switch value := value.(type) {
+ case string:
+ if needsQuoting(value) {
+ b.WriteString(value)
+ } else {
+ fmt.Fprintf(b, "%q", value)
+ }
+ case error:
+ errmsg := value.Error()
+ if needsQuoting(errmsg) {
+ b.WriteString(errmsg)
+ } else {
+ fmt.Fprintf(b, "%q", value)
+ }
+ default:
+ fmt.Fprint(b, value)
+ }
+
+ b.WriteByte(' ')
+}
diff --git a/vendor/github.com/Sirupsen/logrus/writer.go b/vendor/github.com/Sirupsen/logrus/writer.go
new file mode 100644
index 0000000000..1e30b1c753
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/writer.go
@@ -0,0 +1,31 @@
+package logrus
+
+import (
+ "bufio"
+ "io"
+ "runtime"
+)
+
+func (logger *Logger) Writer() *io.PipeWriter {
+ reader, writer := io.Pipe()
+
+ go logger.writerScanner(reader)
+ runtime.SetFinalizer(writer, writerFinalizer)
+
+ return writer
+}
+
+func (logger *Logger) writerScanner(reader *io.PipeReader) {
+ scanner := bufio.NewScanner(reader)
+ for scanner.Scan() {
+ logger.Print(scanner.Text())
+ }
+ if err := scanner.Err(); err != nil {
+ logger.Errorf("Error while reading from Writer: %s", err)
+ }
+ reader.Close()
+}
+
+func writerFinalizer(writer *io.PipeWriter) {
+ writer.Close()
+}