diff --git a/README.md b/README.md index 53ef269..c608150 100644 --- a/README.md +++ b/README.md @@ -70,11 +70,20 @@ The `Exit` function can be used to cause an exit with the given status code. ```go check.Exit(check.OK, fmt.Sprintf("Everything is fine - value=%d", 42)) // OK, 0 -// With perfdata -check.Exit(check.Critical, "CRITICAL", "|", "percent_packet_loss=100") // CRITICAL, 2 +// With critical +check.Exit(check.Critical, "Everything is broken", "Do something!") // CRITICAL, 2 ``` -`ExitError` can be used to cause an exit with the given error. +The `ExitWithPerfdata` function can be used to include performance data in the output. + +```go +perfdata := check.PerfdataList{} +perfdata.Add(&check.Perfdata{Label: "packages_lost", Value: 42}) + +check.ExitWithPerfdata(check.OK, perfdata, "Everything is fine", "Package loss nominal") +``` + +The `ExitError` function can be used to cause an exit with the given error. ```go err := fmt.Errorf("connection to %s has been timed out", "localhost:12345") @@ -118,9 +127,9 @@ See also: https://www.monitoring-plugins.org/doc/guidelines.html#THRESHOLDFORMAT The `Perfdata` object represents monitoring plugin performance data that relates to the actual execution of a host or service check. ```go -var pl perfdata.PerfdataList +var pl check.PerfdataList -pl.Add(&perfdata.Perfdata{ +pl.Add(&check.Perfdata{ Label: "process.cpu.percent", Value: 25, Uom: "%", diff --git a/examples/check_example2/main.go b/examples/check_example2/main.go index 0b1a756..ccba7dc 100644 --- a/examples/check_example2/main.go +++ b/examples/check_example2/main.go @@ -2,7 +2,6 @@ package main import ( "github.com/NETWAYS/go-check" - "github.com/NETWAYS/go-check/perfdata" "github.com/NETWAYS/go-check/result" ) @@ -16,7 +15,7 @@ func main() { check1.Output = "Check1" check1.SetState(check.OK) - check1.Perfdata.Add(&perfdata.Perfdata{ + check1.Perfdata.Add(&check.Perfdata{ Label: "foo", Value: 23, }) @@ -28,11 +27,11 @@ func main() { check2.Output = "Check2" check2.SetState(check.Warning) - check2.Perfdata.Add(&perfdata.Perfdata{ + check2.Perfdata.Add(&check.Perfdata{ Label: "bar", Value: 42, }) - check2.Perfdata.Add(&perfdata.Perfdata{ + check2.Perfdata.Add(&check.Perfdata{ Label: "foo2 bar", Value: 46, }) diff --git a/exit.go b/exit.go index 33b25ff..76e3c78 100644 --- a/exit.go +++ b/exit.go @@ -15,7 +15,9 @@ var AllowExit = true var PrintStack = true // Exit exits the process with a given return code determined from the given Status -// and a text representation to stdout. +// and the provided text output to stdout. +// To include performance that is recommended to use ExitWithPerfdata instead +// Note that, the text output is not sanitized and will be printed as is. // // Example: [OK] - everything is fine // exit 0 @@ -37,6 +39,34 @@ func Exit(rc Status, output ...string) { BaseExit(rc) } +// ExitWithPerfdata exits the process with a given return code determined from the Status, +// the performance data and the text output to stdout. +// +// The provided text output will be sanitized to avoid multiple performance data +// separators (|). +// +// Example: [OK] - everything is fine | mylabel=1 +// exit 0 +func ExitWithPerfdata(rc Status, perfdata PerfdataList, output ...string) { + var text strings.Builder + + text.WriteString("[" + rc.String() + "] -") + + for _, s := range output { + text.WriteString(" " + strings.ReplaceAll(s, PerfdataSeparatorSymbol, " ")) + } + + text.WriteString(PerfdataSeparatorSymbol) + + text.WriteString(perfdata.String()) + + text.WriteString("\n") + + _, _ = os.Stdout.WriteString(text.String()) + + BaseExit(rc) +} + // BaseExit exits the process with a given return code. // // Can be controlled with the global AllowExit. diff --git a/exit_test.go b/exit_test.go index efcf558..6e1f38b 100644 --- a/exit_test.go +++ b/exit_test.go @@ -12,6 +12,16 @@ func ExampleExit() { // would exit with code 0 } +func ExampleExitWithPerfdata() { + perfdata := PerfdataList{} + perfdata.Add(&Perfdata{Label: "time_duration", Value: 23}) + perfdata.Add(&Perfdata{Label: "packages_lost", Value: 42}) + + ExitWithPerfdata(Critical, perfdata, "Everything is broken", "Do something") + // Output: [CRITICAL] - Everything is broken Do something|time_duration=23 packages_lost=42 + // would exit with code 2 +} + func ExampleExitError() { err := fmt.Errorf("connection to %s has been timed out", "localhost:12345") ExitError(err) diff --git a/perfdata/perfdata.go b/perfdata.go similarity index 91% rename from perfdata/perfdata.go rename to perfdata.go index 6487774..ed92d03 100644 --- a/perfdata/perfdata.go +++ b/perfdata.go @@ -1,18 +1,17 @@ -// Package perfdata provides types and functions to handle check plugin performance data -package perfdata +package check import ( "errors" "fmt" "math" "strings" - - "github.com/NETWAYS/go-check" ) +const PerfdataSeparatorSymbol = "|" + // PerfdataList can store multiple perfdata and implements the fmt.Stringer interface // to provide formated output for the performance data -type PerfdataList []*Perfdata //nolint: revive +type PerfdataList []*Perfdata // String returns string representations of all Perfdata added to the list func (l *PerfdataList) String() string { @@ -57,7 +56,7 @@ func formatNumeric(value any) (string, error) { return "", errors.New("Perfdata value is infinite") } - return check.FormatFloat(v), nil + return FormatFloat(v), nil case float32: if math.IsInf(float64(v), 0) { return "", errors.New("Perfdata value is infinite") @@ -67,7 +66,7 @@ func formatNumeric(value any) (string, error) { return "", errors.New("Perfdata value is infinite") } - return check.FormatFloat(float64(v)), nil + return FormatFloat(float64(v)), nil case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: return fmt.Sprintf("%d", v), nil default: @@ -88,8 +87,8 @@ type Perfdata struct { Value any // Uom is the unit-of-measurement, see links above for details. Uom string - Warn *check.Threshold - Crit *check.Threshold + Warn *Threshold + Crit *Threshold Min any Max any } @@ -124,7 +123,7 @@ func (p Perfdata) ValidatedString() (string, error) { sb.WriteString(p.Uom) // Thresholds - for _, value := range []*check.Threshold{p.Warn, p.Crit} { + for _, value := range []*Threshold{p.Warn, p.Crit} { sb.WriteString(";") if value != nil { diff --git a/perfdata/perfdata_test.go b/perfdata_test.go similarity index 92% rename from perfdata/perfdata_test.go rename to perfdata_test.go index b7f89af..7e9e3fb 100644 --- a/perfdata/perfdata_test.go +++ b/perfdata_test.go @@ -1,11 +1,9 @@ -package perfdata +package check import ( "fmt" "math" "testing" - - "github.com/NETWAYS/go-check" ) func ExamplePerfdataList() { @@ -52,8 +50,8 @@ func BenchmarkPerfdataString(b *testing.B) { Label: "test test=test", Value: 10.1, Uom: "%", - Warn: &check.Threshold{Upper: 80}, - Crit: &check.Threshold{Upper: 90}, + Warn: &Threshold{Upper: 80}, + Crit: &Threshold{Upper: 90}, Min: 0, Max: 100} @@ -102,8 +100,8 @@ func TestRenderPerfdata(t *testing.T) { Label: "foo bar", Value: 2.76, Uom: "m", - Warn: &check.Threshold{Lower: 10, Upper: 25, Inside: true}, - Crit: &check.Threshold{Lower: 15, Upper: 20, Inside: false}, + Warn: &Threshold{Lower: 10, Upper: 25, Inside: true}, + Crit: &Threshold{Lower: 15, Upper: 20, Inside: false}, }, expected: "'foo bar'=2.76m;@10:25;15:20", }, diff --git a/result/overall.go b/result/overall.go index 1227836..4861c25 100644 --- a/result/overall.go +++ b/result/overall.go @@ -7,7 +7,6 @@ import ( "sync" "github.com/NETWAYS/go-check" - "github.com/NETWAYS/go-check/perfdata" ) // The "width" of the indentation which is added on every level @@ -100,14 +99,14 @@ func (o *Overall) GetOutput() string { // Generate indeted output and perfdata for all partialResults for i := range o.PartialResults { - output.WriteString(o.PartialResults[i].getOutput(0)) + output.WriteString(strings.ReplaceAll(o.PartialResults[i].getOutput(0), check.PerfdataSeparatorSymbol, " ")) pdata.WriteString(" " + o.PartialResults[i].getPerfdata()) } pdataString := strings.Trim(pdata.String(), " ") if len(pdataString) > 0 { - output.WriteString("|" + pdataString + "\n") + output.WriteString(check.PerfdataSeparatorSymbol + pdataString + "\n") } } @@ -144,7 +143,7 @@ func (o *Overall) getStatusCount() statusCount { // PartialResult represents a sub-result for an Overall struct type PartialResult struct { - Perfdata perfdata.PerfdataList + Perfdata check.PerfdataList PartialResults []*PartialResult Output string @@ -183,7 +182,7 @@ func (s *PartialResult) AddSubcheck(subcheck *PartialResult) { // String returns the status and output of the PartialResult func (s *PartialResult) String() string { - return fmt.Sprintf("[%s] %s", s.GetStatus(), s.Output) + return fmt.Sprintf("[%s] %s", s.GetStatus(), strings.ReplaceAll(s.Output, check.PerfdataSeparatorSymbol, " ")) } // SetDefaultState sets a new default state for a PartialResult @@ -269,7 +268,7 @@ func (o *Overall) getSummary() string { checkState := o.GetStatus() if checkState == check.OK && o.OKSummary != "" { - return o.OKSummary + return strings.ReplaceAll(o.OKSummary, check.PerfdataSeparatorSymbol, " ") } if len(o.PartialResults) == 0 { diff --git a/result/overall_test.go b/result/overall_test.go index 998073a..371c074 100644 --- a/result/overall_test.go +++ b/result/overall_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/NETWAYS/go-check" - "github.com/NETWAYS/go-check/perfdata" ) func TestOverall_NewPartialResult(t *testing.T) { @@ -150,8 +149,8 @@ func ExampleOverall_GetStatus() { func ExampleOverall_withSubchecks() { var overall Overall - example_perfdata := perfdata.Perfdata{Label: "pd_test", Value: 5, Uom: "s"} - pd_list := perfdata.PerfdataList{} + example_perfdata := check.Perfdata{Label: "pd_test", Value: 5, Uom: "s"} + pd_list := check.PerfdataList{} pd_list.Add(&example_perfdata) subcheck := &PartialResult{ @@ -172,11 +171,38 @@ func ExampleOverall_withSubchecks() { // |pd_test=5s } +func ExampleOverall_withVerticalbar() { + var overall Overall + + overall.OKSummary = "unit|test" + + example_perfdata := check.Perfdata{Label: "pd_test", Value: 5, Uom: "s"} + pd_list := check.PerfdataList{} + pd_list.Add(&example_perfdata) + + subcheck := &PartialResult{ + Output: "vertical|bar", + Perfdata: pd_list, + } + + subcheck.SetState(check.OK) + + overall.AddSubcheck(subcheck) + overall.Add(check.OK, "another|bar") + + fmt.Println(overall.GetOutput()) + // Output: + // unit test + // \_ [OK] vertical bar + // \_ [OK] another bar + // |pd_test=5s +} + func TestOverall_withEnhancedSubchecks(t *testing.T) { var overall Overall - example_perfdata := perfdata.Perfdata{Label: "pd_test", Value: 5, Uom: "s"} - example_perfdata2 := perfdata.Perfdata{ + example_perfdata := check.Perfdata{Label: "pd_test", Value: 5, Uom: "s"} + example_perfdata2 := check.Perfdata{ Label: "pd_test2", Value: 1099511627776, Uom: "kB", @@ -184,14 +210,14 @@ func TestOverall_withEnhancedSubchecks(t *testing.T) { Crit: &check.Threshold{Inside: false, Lower: 07777777777777, Upper: 0xFFFFFFFFFFFFFFFFFFFF}, Max: uint64(18446744073709551615), } - example_perfdata3 := perfdata.Perfdata{Label: "kl;jr2if;l2rkjasdf", Value: 5, Uom: "m"} - example_perfdata4 := perfdata.Perfdata{Label: "asdf", Value: uint64(18446744073709551615), Uom: "B"} + example_perfdata3 := check.Perfdata{Label: "kl;jr2if;l2rkjasdf", Value: 5, Uom: "m"} + example_perfdata4 := check.Perfdata{Label: "asdf", Value: uint64(18446744073709551615), Uom: "B"} - pd_list := perfdata.PerfdataList{} + pd_list := check.PerfdataList{} pd_list.Add(&example_perfdata) pd_list.Add(&example_perfdata2) - pd_list2 := perfdata.PerfdataList{} + pd_list2 := check.PerfdataList{} pd_list2.Add(&example_perfdata3) pd_list2.Add(&example_perfdata4) @@ -275,11 +301,11 @@ func TestOverall_withSubchecks_Perfdata(t *testing.T) { subcheck.SetState(check.OK) - perf1 := perfdata.Perfdata{ + perf1 := check.Perfdata{ Label: "foo", Value: 3, } - perf2 := perfdata.Perfdata{ + perf2 := check.Perfdata{ Label: "bar", Value: 300, Uom: "%", @@ -323,16 +349,16 @@ func TestOverall_withSubchecks_PartialResult(t *testing.T) { Output: "PartialResult", } - perf1 := perfdata.Perfdata{ + perf1 := check.Perfdata{ Label: "foo", Value: 3, } - perf2 := perfdata.Perfdata{ + perf2 := check.Perfdata{ Label: "bar", Value: 300, Uom: "%", } - perf3 := perfdata.Perfdata{ + perf3 := check.Perfdata{ Label: "baz", Value: 23, Uom: "B", @@ -407,12 +433,12 @@ func TestSubchecksPerfdata(t *testing.T) { check1 := &PartialResult{ Output: "Check1", - Perfdata: perfdata.PerfdataList{ - &perfdata.Perfdata{ + Perfdata: check.PerfdataList{ + &check.Perfdata{ Label: "foo", Value: 23, }, - &perfdata.Perfdata{ + &check.Perfdata{ Label: "bar", Value: 42, }, @@ -423,8 +449,8 @@ func TestSubchecksPerfdata(t *testing.T) { check2 := &PartialResult{ Output: "Check2", - Perfdata: perfdata.PerfdataList{ - &perfdata.Perfdata{ + Perfdata: check.PerfdataList{ + &check.Perfdata{ Label: "foo2 bar", Value: 46, },