diff --git a/cmd/telegraf/telegraf.go b/cmd/telegraf/telegraf.go index 7e0b4ec1ca67a..c6b2cc0eef41b 100644 --- a/cmd/telegraf/telegraf.go +++ b/cmd/telegraf/telegraf.go @@ -15,6 +15,7 @@ import ( "syscall" "time" + "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/agent" "github.com/influxdata/telegraf/config" "github.com/influxdata/telegraf/internal" @@ -158,8 +159,9 @@ func runAgent(ctx context.Context, } // Setup logging as configured. + telegraf.Debug = ag.Config.Agent.Debug || *fDebug logConfig := logger.LogConfig{ - Debug: ag.Config.Agent.Debug || *fDebug, + Debug: telegraf.Debug, Quiet: ag.Config.Agent.Quiet || *fQuiet, LogTarget: ag.Config.Agent.LogTarget, Logfile: ag.Config.Agent.Logfile, diff --git a/plugin.go b/plugin.go index 0793fbb061115..f9dcaeac0344c 100644 --- a/plugin.go +++ b/plugin.go @@ -1,5 +1,7 @@ package telegraf +var Debug bool + // Initializer is an interface that all plugin types: Inputs, Outputs, // Processors, and Aggregators can optionally implement to initialize the // plugin. @@ -21,7 +23,7 @@ type PluginDescriber interface { Description() string } -// Logger defines an interface for logging. +// Logger defines an plugin-related interface for logging. type Logger interface { // Errorf logs an error message, patterned after log.Printf. Errorf(format string, args ...interface{}) diff --git a/plugins/inputs/exec/exec.go b/plugins/inputs/exec/exec.go index 26e2ab0ba0301..fc498c799c966 100644 --- a/plugins/inputs/exec/exec.go +++ b/plugins/inputs/exec/exec.go @@ -3,6 +3,7 @@ package exec import ( "bytes" "fmt" + "io" "os/exec" "path/filepath" "runtime" @@ -39,12 +40,12 @@ const sampleConfig = ` data_format = "influx" ` -const MaxStderrBytes = 512 +const MaxStderrBytes int = 512 type Exec struct { - Commands []string - Command string - Timeout internal.Duration + Commands []string `toml:"commands"` + Command string `toml:"command"` + Timeout internal.Duration `toml:"timeout"` parser parsers.Parser @@ -85,16 +86,16 @@ func (c CommandRunner) Run( runErr := internal.RunTimeout(cmd, timeout) - out = removeCarriageReturns(out) - if stderr.Len() > 0 { - stderr = removeCarriageReturns(stderr) - stderr = truncate(stderr) + out = removeWindowsCarriageReturns(out) + if stderr.Len() > 0 && !telegraf.Debug { + stderr = removeWindowsCarriageReturns(stderr) + stderr = c.truncate(stderr) } return out.Bytes(), stderr.Bytes(), runErr } -func truncate(buf bytes.Buffer) bytes.Buffer { +func (c CommandRunner) truncate(buf bytes.Buffer) bytes.Buffer { // Limit the number of bytes. didTruncate := false if buf.Len() > MaxStderrBytes { @@ -114,27 +115,21 @@ func truncate(buf bytes.Buffer) bytes.Buffer { return buf } -// removeCarriageReturns removes all carriage returns from the input if the +// removeWindowsCarriageReturns removes all carriage returns from the input if the // OS is Windows. It does not return any errors. -func removeCarriageReturns(b bytes.Buffer) bytes.Buffer { +func removeWindowsCarriageReturns(b bytes.Buffer) bytes.Buffer { if runtime.GOOS == "windows" { var buf bytes.Buffer for { - byt, er := b.ReadBytes(0x0D) - end := len(byt) - if nil == er { - end-- + byt, err := b.ReadBytes(0x0D) + byt = bytes.TrimRight(byt, "\x0d") + if len(byt) > 0 { + _, _ = buf.Write(byt) } - if nil != byt { - buf.Write(byt[:end]) - } else { - break - } - if nil != er { - break + if err == io.EOF { + return buf } } - b = buf } return b } diff --git a/plugins/inputs/exec/exec_test.go b/plugins/inputs/exec/exec_test.go index ba1bc2078c9f7..8d77f0cef4757 100644 --- a/plugins/inputs/exec/exec_test.go +++ b/plugins/inputs/exec/exec_test.go @@ -259,9 +259,10 @@ func TestTruncate(t *testing.T) { }, } + c := CommandRunner{} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - res := truncate(*tt.bufF()) + res := c.truncate(*tt.bufF()) require.Equal(t, tt.expF().Bytes(), res.Bytes()) }) } @@ -272,14 +273,14 @@ func TestRemoveCarriageReturns(t *testing.T) { // Test that all carriage returns are removed for _, test := range crTests { b := bytes.NewBuffer(test.input) - out := removeCarriageReturns(*b) + out := removeWindowsCarriageReturns(*b) assert.True(t, bytes.Equal(test.output, out.Bytes())) } } else { // Test that the buffer is returned unaltered for _, test := range crTests { b := bytes.NewBuffer(test.input) - out := removeCarriageReturns(*b) + out := removeWindowsCarriageReturns(*b) assert.True(t, bytes.Equal(test.input, out.Bytes())) } } diff --git a/plugins/outputs/exec/README.md b/plugins/outputs/exec/README.md index d82676a251e4e..7e19b9a8475c6 100644 --- a/plugins/outputs/exec/README.md +++ b/plugins/outputs/exec/README.md @@ -8,6 +8,8 @@ The command should be defined similar to docker's `exec` form: On non-zero exit stderr will be logged at error level. +For better performance, consider execd, which runs continuously. + ### Configuration ```toml diff --git a/plugins/outputs/exec/exec.go b/plugins/outputs/exec/exec.go index 813b6bb9f54a7..25637bd1984c0 100644 --- a/plugins/outputs/exec/exec.go +++ b/plugins/outputs/exec/exec.go @@ -6,6 +6,7 @@ import ( "io" "log" "os/exec" + "runtime" "time" "github.com/influxdata/telegraf" @@ -39,6 +40,10 @@ var sampleConfig = ` # data_format = "influx" ` +func (e *Exec) Init() error { + return nil +} + // SetSerializer sets the serializer for the output. func (e *Exec) SetSerializer(serializer serializers.Serializer) { e.serializer = serializer @@ -105,8 +110,13 @@ func (c *CommandRunner) Run(timeout time.Duration, command []string, buffer io.R return fmt.Errorf("%q timed out and was killed", command) } + s = removeWindowsCarriageReturns(s) if s.Len() > 0 { - log.Printf("E! [outputs.exec] Command error: %q", truncate(s)) + if !telegraf.Debug { + log.Printf("E! [outputs.exec] Command error: %q", c.truncate(s)) + } else { + log.Printf("D! [outputs.exec] Command error: %q", s) + } } if status, ok := internal.ExitStatus(err); ok { @@ -121,7 +131,7 @@ func (c *CommandRunner) Run(timeout time.Duration, command []string, buffer io.R return nil } -func truncate(buf bytes.Buffer) string { +func (c *CommandRunner) truncate(buf bytes.Buffer) string { // Limit the number of bytes. didTruncate := false if buf.Len() > maxStderrBytes { @@ -149,3 +159,22 @@ func init() { } }) } + +// removeWindowsCarriageReturns removes all carriage returns from the input if the +// OS is Windows. It does not return any errors. +func removeWindowsCarriageReturns(b bytes.Buffer) bytes.Buffer { + if runtime.GOOS == "windows" { + var buf bytes.Buffer + for { + byt, err := b.ReadBytes(0x0D) + byt = bytes.TrimRight(byt, "\x0d") + if len(byt) > 0 { + _, _ = buf.Write(byt) + } + if err == io.EOF { + return buf + } + } + } + return b +} diff --git a/plugins/outputs/exec/exec_test.go b/plugins/outputs/exec/exec_test.go index 5758c307b56e7..f57bf50a1b571 100644 --- a/plugins/outputs/exec/exec_test.go +++ b/plugins/outputs/exec/exec_test.go @@ -83,9 +83,10 @@ func TestTruncate(t *testing.T) { len: len("hola") + len("..."), }, } + c := CommandRunner{} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := truncate(*tt.buf) + s := c.truncate(*tt.buf) require.Equal(t, tt.len, len(s)) }) }