Skip to content

Commit fde1f6e

Browse files
authored
logger_cb: remove default callbacks (#294)
The logic related to filtering by callbacks must be a decision of the library consumers, as it is not possible to know which is the best strategy for each case. This also adds to the related selftest other example that shows how to filter using callbacks. Be warned that if filters are not defined via callbacks, all output is passed to logFallback() which outputs to stderr.
1 parent 8f83f25 commit fde1f6e

3 files changed

Lines changed: 83 additions & 116 deletions

File tree

logger_cb.go

Lines changed: 10 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import "C"
88
import (
99
"fmt"
1010
"os"
11-
"regexp"
1211
"strings"
1312
)
1413

@@ -47,102 +46,30 @@ const (
4746

4847
// Callbacks stores the callbacks to be used by libbpfgo
4948
type Callbacks struct {
50-
Log func(level int, msg string, keyValues ...interface{})
49+
Log func(level int, msg string)
5150
LogFilters []func(libLevel int, msg string) bool
5251
}
5352

5453
// callbacks is initialized with default callbacks, but can be changed by SetLoggerCbs
5554
var callbacks = Callbacks{
56-
Log: logFallback,
57-
LogFilters: []func(libLevel int, msg string) bool{
58-
LogFilterLevel,
59-
LogFilterOutput,
60-
},
55+
Log: logFallback,
56+
LogFilters: []func(libLevel int, msg string) bool{},
6157
}
6258

6359
// SetLoggerCbs receives Callbacks type to be used to log libbpf outputs and to filter out those outputs
6460
func SetLoggerCbs(cbs Callbacks) {
65-
if cbs.Log == nil {
61+
if cbs.Log == nil { // guarantee that there is always an outputter
6662
cbs.Log = logFallback
6763
}
6864

6965
callbacks = cbs
7066
}
7167

72-
// logFallback:
73-
// - level is ignored in this stage
74-
// - type coercion only takes care of string types
75-
// - keyValues is not required to contain pairs
76-
// - outputs all to stderr
77-
func logFallback(level int, msg string, keyValues ...interface{}) {
78-
var (
79-
args = make([]string, 0)
80-
outMsg = msg
81-
)
82-
83-
for _, v := range keyValues {
84-
if s, ok := v.(string); ok {
85-
outMsg += " [%s]"
86-
args = append(args, s)
87-
}
88-
}
89-
90-
outMsg += "\n"
91-
if len(keyValues) > 0 {
92-
fmt.Fprintf(os.Stderr, outMsg, args)
93-
} else {
94-
fmt.Fprint(os.Stderr, outMsg)
95-
}
96-
}
97-
98-
// LogFilterLevel filters by checking its print level
99-
// In case the consumer defines its own filters functions via SetLoggerCbs, this can also be passed
100-
func LogFilterLevel(libbpfPrintLevel int, output string) bool {
101-
return libbpfPrintLevel != LibbpfWarnLevel
102-
}
103-
104-
var (
105-
// triggered by: libbpf/src/nlattr.c->libbpf_nla_dump_errormsg()
106-
// "libbpf: Kernel error message: %s\n"
107-
// 1. %s = "Exclusivity flag on"
108-
regexKernelExclusivityFlagOn = regexp.MustCompile(`libbpf:.*Kernel error message:.*Exclusivity flag on`)
109-
110-
// triggered by: libbpf/src/libbpf.c->bpf_program__attach_kprobe_opts()
111-
// "libbpf: prog '%s': failed to create %s '%s+0x%zx' perf event: %s\n"
112-
// 1. %s = trace_check_map_func_compatibility
113-
// 2. %s = kretprobe or kprobe
114-
// 3. %s = check_map_func_compatibility (function name)
115-
// 4. %x = offset (ignored in this check)
116-
// 5. %s = No such file or directory
117-
regexKprobePerfEvent = regexp.MustCompile(`libbpf:.*prog 'trace_check_map_func_compatibility'.*failed to create kprobe.*perf event: No such file or directory`)
118-
119-
// triggered by: libbpf/src/libbpf.c->bpf_program__attach_fd()
120-
// "libbpf: prog '%s': failed to attach to %s: %s\n"
121-
// 1. %s = cgroup_skb_ingress or cgroup_skb_egress
122-
// 2. %s = cgroup
123-
// 3. %s = Invalid argument
124-
regexAttachCgroup = regexp.MustCompile(`libbpf:.*prog 'cgroup_skb_ingress|cgroup_skb_egress'.*failed to attach to cgroup.*Invalid argument`)
125-
)
126-
127-
// LogFilterOutput filters out some errors by using regex
128-
// In case the consumer defines its own filters functions via SetLoggerCbs, this can also be passed
129-
func LogFilterOutput(libbpfPrintLevel int, output string) bool {
130-
// BUG: https:/github.com/aquasecurity/tracee/issues/1676
131-
if regexKernelExclusivityFlagOn.MatchString(output) {
132-
return true
133-
}
134-
135-
// BUG: https://github.com/aquasecurity/tracee/issues/2446
136-
if regexKprobePerfEvent.MatchString(output) {
137-
return true
138-
}
139-
140-
// AttachCgroupLegacy() will first try AttachCgroup() and it might fail. This
141-
// is not an error and is the best way of probing for eBPF cgroup attachment
142-
// link existence.
143-
if regexAttachCgroup.MatchString(output) {
144-
return true
145-
}
68+
// logFallback is the default logger callback
69+
// - level is ignored
70+
// - output, suffixed with a newline, is printed to stderr
71+
func logFallback(level int, msg string) {
72+
var outMsg = msg + "\n"
14673

147-
return false
74+
fmt.Fprint(os.Stderr, outMsg)
14875
}

logger_cb_test.go

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,52 @@
11
package libbpfgo
22

3-
import "testing"
3+
import (
4+
"bytes"
5+
"io"
6+
"os"
7+
"testing"
48

5-
func TestLogFilterOutput(t *testing.T) {
6-
tests := []struct {
7-
libbpfPrintLevel int
8-
output string
9-
expectedResult bool
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestLogFallback(t *testing.T) {
14+
tt := []struct {
15+
message string
1016
}{
1117
{
12-
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",
13-
expectedResult: true,
14-
},
15-
{
16-
output: "libbpf: Kernel error message: Exclusivity flag on\n",
17-
expectedResult: true,
18-
},
19-
{
20-
output: "libbpf: prog 'cgroup_skb_ingress': failed to attach to cgroup 'cgroup': Invalid argument\n",
21-
expectedResult: true,
22-
},
23-
{
24-
output: "libbpf: prog 'cgroup_skb_egress': failed to attach to cgroup 'cgroup': Invalid argument\n",
25-
expectedResult: true,
18+
message: "This is a warning message",
2619
},
2720
{
28-
output: "This is not a log message that should be filtered\n",
29-
expectedResult: false,
21+
message: "This is a information message",
3022
},
3123
{
32-
output: "libbpf: This is not a log message that should be filtered\n",
33-
expectedResult: false,
24+
message: "This is a debug message",
3425
},
3526
}
3627

37-
for _, test := range tests {
38-
result := LogFilterOutput(test.libbpfPrintLevel, test.output)
39-
if result != test.expectedResult {
40-
t.Errorf("For input '%s', expected %v but got %v", test.output, test.expectedResult, result)
41-
}
28+
for _, tc := range tt {
29+
var buf bytes.Buffer
30+
31+
r, w, err := os.Pipe()
32+
require.NoError(t, err, "failed to create pipe")
33+
34+
writeEnd := os.NewFile(uintptr(w.Fd()), "pipe")
35+
36+
oldStderr := os.Stderr
37+
os.Stderr = writeEnd
38+
39+
// level is ignored
40+
logFallback(LibbpfInfoLevel, tc.message)
41+
42+
os.Stderr = oldStderr
43+
44+
err = writeEnd.Close()
45+
require.NoError(t, err, "failed to close writeEnd")
46+
_, err = io.Copy(&buf, r)
47+
require.NoError(t, err, "failed to copy from read end to buffer")
48+
49+
// The message should be printed to stderr with a newline
50+
assert.Equal(t, tc.message+"\n", buf.String())
4251
}
4352
}

selftest/log-callbacks/main.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,20 @@ import (
1313

1414
var logOutput []string
1515

16-
func log(level int, msg string, keyValues ...interface{}) {
16+
// log is a handler to save the log output
17+
func log(level int, msg string) {
1718
logOutput = append(logOutput, msg)
1819
}
1920

2021
func main() {
22+
//
23+
// Filter example 1: filter out all outputs but containing "found program 'kprobe__sys_mmap'"
24+
//
2125
filterMatch := "found program 'kprobe__sys_mmap'"
2226
bpf.SetLoggerCbs(bpf.Callbacks{
23-
Log: log, // use log() as a handler for libbpf outputs that are not excluded by LogFilters
27+
Log: log,
2428
LogFilters: []func(libLevel int, msg string) bool{
2529
func(libLevel int, msg string) bool {
26-
// filter all output but containing "found program 'kprobe__sys_mmap'"
2730
return !strings.Contains(msg, filterMatch)
2831
},
2932
},
@@ -34,11 +37,39 @@ func main() {
3437
fmt.Fprintln(os.Stderr, err)
3538
os.Exit(-1)
3639
}
37-
defer bpfModule.Close()
40+
bpfModule.Close()
3841

3942
if len(logOutput) != 1 {
4043
fmt.Fprintln(os.Stderr, fmt.Sprintf("Log output should contain only one output matching the string: %s", filterMatch))
4144
fmt.Fprintln(os.Stderr, fmt.Sprintf("Log output: %v", logOutput))
4245
os.Exit(-1)
4346
}
47+
48+
// clean logOutput
49+
logOutput = []string{}
50+
51+
//
52+
// Filter example 2: filter out all outputs which level is LibbpfDebugLevel
53+
//
54+
bpf.SetLoggerCbs(bpf.Callbacks{
55+
Log: log,
56+
LogFilters: []func(libLevel int, msg string) bool{
57+
func(libLevel int, msg string) bool {
58+
return libLevel == bpf.LibbpfDebugLevel
59+
},
60+
},
61+
})
62+
63+
bpfModule, err = bpf.NewModuleFromFile("main.bpf.o")
64+
if err != nil {
65+
fmt.Fprintln(os.Stderr, err)
66+
os.Exit(-1)
67+
}
68+
bpfModule.Close()
69+
70+
if len(logOutput) != 0 {
71+
fmt.Fprintln(os.Stderr, "Log output should be empty")
72+
fmt.Fprintln(os.Stderr, fmt.Sprintf("Log output: %v", logOutput))
73+
os.Exit(-1)
74+
}
4475
}

0 commit comments

Comments
 (0)