diff --git a/logger_cb.go b/logger_cb.go index adc2e1c1..4c200e13 100644 --- a/logger_cb.go +++ b/logger_cb.go @@ -8,7 +8,6 @@ import "C" import ( "fmt" "os" - "regexp" "strings" ) @@ -47,102 +46,30 @@ const ( // Callbacks stores the callbacks to be used by libbpfgo type Callbacks struct { - Log func(level int, msg string, keyValues ...interface{}) + Log func(level int, msg string) LogFilters []func(libLevel int, msg string) bool } // callbacks is initialized with default callbacks, but can be changed by SetLoggerCbs var callbacks = Callbacks{ - Log: logFallback, - LogFilters: []func(libLevel int, msg string) bool{ - LogFilterLevel, - LogFilterOutput, - }, + Log: logFallback, + LogFilters: []func(libLevel int, msg string) bool{}, } // SetLoggerCbs receives Callbacks type to be used to log libbpf outputs and to filter out those outputs func SetLoggerCbs(cbs Callbacks) { - if cbs.Log == nil { + if cbs.Log == nil { // guarantee that there is always an outputter cbs.Log = logFallback } callbacks = cbs } -// logFallback: -// - level is ignored in this stage -// - type coercion only takes care of string types -// - keyValues is not required to contain pairs -// - outputs all to stderr -func logFallback(level int, msg string, keyValues ...interface{}) { - var ( - args = make([]string, 0) - outMsg = msg - ) - - for _, v := range keyValues { - if s, ok := v.(string); ok { - outMsg += " [%s]" - args = append(args, s) - } - } - - outMsg += "\n" - if len(keyValues) > 0 { - fmt.Fprintf(os.Stderr, outMsg, args) - } else { - fmt.Fprint(os.Stderr, outMsg) - } -} - -// LogFilterLevel filters by checking its print level -// In case the consumer defines its own filters functions via SetLoggerCbs, this can also be passed -func LogFilterLevel(libbpfPrintLevel int, output string) bool { - return libbpfPrintLevel != LibbpfWarnLevel -} - -var ( - // triggered by: libbpf/src/nlattr.c->libbpf_nla_dump_errormsg() - // "libbpf: Kernel error message: %s\n" - // 1. %s = "Exclusivity flag on" - regexKernelExclusivityFlagOn = regexp.MustCompile(`libbpf:.*Kernel error message:.*Exclusivity flag on`) - - // triggered by: libbpf/src/libbpf.c->bpf_program__attach_kprobe_opts() - // "libbpf: prog '%s': failed to create %s '%s+0x%zx' perf event: %s\n" - // 1. %s = trace_check_map_func_compatibility - // 2. %s = kretprobe or kprobe - // 3. %s = check_map_func_compatibility (function name) - // 4. %x = offset (ignored in this check) - // 5. %s = No such file or directory - regexKprobePerfEvent = regexp.MustCompile(`libbpf:.*prog 'trace_check_map_func_compatibility'.*failed to create kprobe.*perf event: No such file or directory`) - - // triggered by: libbpf/src/libbpf.c->bpf_program__attach_fd() - // "libbpf: prog '%s': failed to attach to %s: %s\n" - // 1. %s = cgroup_skb_ingress or cgroup_skb_egress - // 2. %s = cgroup - // 3. %s = Invalid argument - regexAttachCgroup = regexp.MustCompile(`libbpf:.*prog 'cgroup_skb_ingress|cgroup_skb_egress'.*failed to attach to cgroup.*Invalid argument`) -) - -// LogFilterOutput filters out some errors by using regex -// In case the consumer defines its own filters functions via SetLoggerCbs, this can also be passed -func LogFilterOutput(libbpfPrintLevel int, output string) bool { - // BUG: https:/github.com/aquasecurity/tracee/issues/1676 - if regexKernelExclusivityFlagOn.MatchString(output) { - return true - } - - // BUG: https://github.com/aquasecurity/tracee/issues/2446 - if regexKprobePerfEvent.MatchString(output) { - return true - } - - // AttachCgroupLegacy() will first try AttachCgroup() and it might fail. This - // is not an error and is the best way of probing for eBPF cgroup attachment - // link existence. - if regexAttachCgroup.MatchString(output) { - return true - } +// logFallback is the default logger callback +// - level is ignored +// - output, suffixed with a newline, is printed to stderr +func logFallback(level int, msg string) { + var outMsg = msg + "\n" - return false + fmt.Fprint(os.Stderr, outMsg) } diff --git a/logger_cb_test.go b/logger_cb_test.go index 3fe7f8cb..d2804cf5 100644 --- a/logger_cb_test.go +++ b/logger_cb_test.go @@ -1,43 +1,52 @@ package libbpfgo -import "testing" +import ( + "bytes" + "io" + "os" + "testing" -func TestLogFilterOutput(t *testing.T) { - tests := []struct { - libbpfPrintLevel int - output string - expectedResult bool + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLogFallback(t *testing.T) { + tt := []struct { + message string }{ { - output: "libbpf: prog 'trace_check_map_func_compatibility': failed to create kprobe 'check_map_func_compatibility+0x0' perf event: No such file or directory\n", - expectedResult: true, - }, - { - output: "libbpf: Kernel error message: Exclusivity flag on\n", - expectedResult: true, - }, - { - output: "libbpf: prog 'cgroup_skb_ingress': failed to attach to cgroup 'cgroup': Invalid argument\n", - expectedResult: true, - }, - { - output: "libbpf: prog 'cgroup_skb_egress': failed to attach to cgroup 'cgroup': Invalid argument\n", - expectedResult: true, + message: "This is a warning message", }, { - output: "This is not a log message that should be filtered\n", - expectedResult: false, + message: "This is a information message", }, { - output: "libbpf: This is not a log message that should be filtered\n", - expectedResult: false, + message: "This is a debug message", }, } - for _, test := range tests { - result := LogFilterOutput(test.libbpfPrintLevel, test.output) - if result != test.expectedResult { - t.Errorf("For input '%s', expected %v but got %v", test.output, test.expectedResult, result) - } + for _, tc := range tt { + var buf bytes.Buffer + + r, w, err := os.Pipe() + require.NoError(t, err, "failed to create pipe") + + writeEnd := os.NewFile(uintptr(w.Fd()), "pipe") + + oldStderr := os.Stderr + os.Stderr = writeEnd + + // level is ignored + logFallback(LibbpfInfoLevel, tc.message) + + os.Stderr = oldStderr + + err = writeEnd.Close() + require.NoError(t, err, "failed to close writeEnd") + _, err = io.Copy(&buf, r) + require.NoError(t, err, "failed to copy from read end to buffer") + + // The message should be printed to stderr with a newline + assert.Equal(t, tc.message+"\n", buf.String()) } } diff --git a/selftest/log-callbacks/main.go b/selftest/log-callbacks/main.go index aef99ffc..dea52868 100644 --- a/selftest/log-callbacks/main.go +++ b/selftest/log-callbacks/main.go @@ -13,17 +13,20 @@ import ( var logOutput []string -func log(level int, msg string, keyValues ...interface{}) { +// log is a handler to save the log output +func log(level int, msg string) { logOutput = append(logOutput, msg) } func main() { + // + // Filter example 1: filter out all outputs but containing "found program 'kprobe__sys_mmap'" + // filterMatch := "found program 'kprobe__sys_mmap'" bpf.SetLoggerCbs(bpf.Callbacks{ - Log: log, // use log() as a handler for libbpf outputs that are not excluded by LogFilters + Log: log, LogFilters: []func(libLevel int, msg string) bool{ func(libLevel int, msg string) bool { - // filter all output but containing "found program 'kprobe__sys_mmap'" return !strings.Contains(msg, filterMatch) }, }, @@ -34,11 +37,39 @@ func main() { fmt.Fprintln(os.Stderr, err) os.Exit(-1) } - defer bpfModule.Close() + bpfModule.Close() if len(logOutput) != 1 { fmt.Fprintln(os.Stderr, fmt.Sprintf("Log output should contain only one output matching the string: %s", filterMatch)) fmt.Fprintln(os.Stderr, fmt.Sprintf("Log output: %v", logOutput)) os.Exit(-1) } + + // clean logOutput + logOutput = []string{} + + // + // Filter example 2: filter out all outputs which level is LibbpfDebugLevel + // + bpf.SetLoggerCbs(bpf.Callbacks{ + Log: log, + LogFilters: []func(libLevel int, msg string) bool{ + func(libLevel int, msg string) bool { + return libLevel == bpf.LibbpfDebugLevel + }, + }, + }) + + bpfModule, err = bpf.NewModuleFromFile("main.bpf.o") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + bpfModule.Close() + + if len(logOutput) != 0 { + fmt.Fprintln(os.Stderr, "Log output should be empty") + fmt.Fprintln(os.Stderr, fmt.Sprintf("Log output: %v", logOutput)) + os.Exit(-1) + } }