-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
deps: remove lfshook in favor of local fileHook #2464
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 7 commits
f5bb22c
0ecc1dc
fa61198
e677dc2
fd4e97c
4baf02e
a53029e
55e95ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| package logging | ||
|
|
||
| import ( | ||
| "os" | ||
| "strings" | ||
| "testing" | ||
|
|
||
| "github.com/sirupsen/logrus" | ||
| ) | ||
|
|
||
| func TestFileHookLevels(t *testing.T) { | ||
| h := &fileHook{ | ||
| path: "/dev/null", | ||
| formatter: &logrus.TextFormatter{}, | ||
| } | ||
| got := h.Levels() | ||
| if len(got) != len(logrus.AllLevels) { | ||
| t.Errorf("Levels() returned %d levels, want %d", len(got), len(logrus.AllLevels)) | ||
| } | ||
| for i, l := range logrus.AllLevels { | ||
| if got[i] != l { | ||
| t.Errorf("Levels()[%d] = %v, want %v", i, got[i], l) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestFileHookFire(t *testing.T) { | ||
| tmpFile, err := os.CreateTemp(t.TempDir(), "filehook-*.log") | ||
| if err != nil { | ||
| t.Fatalf("failed to create temp file: %v", err) | ||
| } | ||
| if err := tmpFile.Close(); err != nil { | ||
| t.Fatalf("failed to close temp file: %v", err) | ||
| } | ||
|
|
||
| h := &fileHook{ | ||
| path: tmpFile.Name(), | ||
| formatter: &logrus.TextFormatter{DisableColors: true, DisableTimestamp: true}, | ||
| } | ||
|
|
||
| entry := &logrus.Entry{ | ||
| Logger: logrus.New(), | ||
| Level: logrus.InfoLevel, | ||
| Message: "hello from fire", | ||
| Data: logrus.Fields{}, | ||
| } | ||
|
|
||
| if err := h.Fire(entry); err != nil { | ||
| t.Fatalf("Fire() returned error: %v", err) | ||
| } | ||
|
|
||
| content, err := os.ReadFile(tmpFile.Name()) | ||
| if err != nil { | ||
| t.Fatalf("failed to read log file: %v", err) | ||
| } | ||
| if !strings.Contains(string(content), "hello from fire") { | ||
| t.Errorf("log file does not contain expected message, got: %q", string(content)) | ||
| } | ||
| } | ||
|
|
||
| func TestFileHookFireAppends(t *testing.T) { | ||
| tmpFile, err := os.CreateTemp(t.TempDir(), "filehook-*.log") | ||
| if err != nil { | ||
| t.Fatalf("failed to create temp file: %v", err) | ||
| } | ||
| if err := tmpFile.Close(); err != nil { | ||
| t.Fatalf("failed to close temp file: %v", err) | ||
| } | ||
|
|
||
| h := &fileHook{ | ||
| path: tmpFile.Name(), | ||
| formatter: &logrus.TextFormatter{DisableColors: true, DisableTimestamp: true}, | ||
| } | ||
|
|
||
| for i, msg := range []string{"first message", "second message", "third message"} { | ||
| entry := &logrus.Entry{ | ||
| Logger: logrus.New(), | ||
| Level: logrus.InfoLevel, | ||
| Message: msg, | ||
| Data: logrus.Fields{}, | ||
| } | ||
| if err := h.Fire(entry); err != nil { | ||
| t.Fatalf("Fire() call %d returned error: %v", i, err) | ||
| } | ||
| } | ||
|
|
||
| content, err := os.ReadFile(tmpFile.Name()) | ||
| if err != nil { | ||
| t.Fatalf("failed to read log file: %v", err) | ||
| } | ||
| s := string(content) | ||
| for _, msg := range []string{"first message", "second message", "third message"} { | ||
| if !strings.Contains(s, msg) { | ||
| t.Errorf("log file missing %q, got: %q", msg, s) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestFileHookIntegration(t *testing.T) { | ||
| tmpFile, err := os.CreateTemp(t.TempDir(), "filehook-*.log") | ||
| if err != nil { | ||
| t.Fatalf("failed to create temp file: %v", err) | ||
| } | ||
| if err := tmpFile.Close(); err != nil { | ||
| t.Fatalf("failed to close temp file: %v", err) | ||
| } | ||
|
|
||
| logger := logrus.New() | ||
| logger.Out = os.Stderr | ||
| logger.Level = logrus.DebugLevel | ||
|
|
||
| logger.Hooks.Add(&fileHook{ | ||
| path: tmpFile.Name(), | ||
| formatter: &logrus.TextFormatter{DisableColors: true, DisableTimestamp: true}, | ||
| }) | ||
|
|
||
| logger.Info("integration info message") | ||
| logger.Warn("integration warn message") | ||
|
|
||
| content, err := os.ReadFile(tmpFile.Name()) | ||
| if err != nil { | ||
| t.Fatalf("failed to read log file: %v", err) | ||
| } | ||
| s := string(content) | ||
| if !strings.Contains(s, "integration info message") { | ||
| t.Errorf("log file missing info message, got: %q", s) | ||
| } | ||
| if !strings.Contains(s, "integration warn message") { | ||
| t.Errorf("log file missing warn message, got: %q", s) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,12 +9,35 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "runtime" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/k0kubun/pp" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/rifflock/lfshook" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/sirupsen/logrus" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| formatter "github.com/kotakanbe/logrus-prefixed-formatter" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type fileHook struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| path string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| formatter logrus.Formatter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (h *fileHook) Levels() []logrus.Level { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return logrus.AllLevels | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return logrus.AllLevels | |
| levels := make([]logrus.Level, len(logrus.AllLevels)) | |
| copy(levels, logrus.AllLevels) | |
| return levels |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not fixing. fileHook is an unexported type, so Levels() is only called by the logrus hook dispatch internally. No external caller can mutate the returned slice. Defensive copying would add an allocation on every log entry for a scenario that cannot occur in practice.
Outdated
Copilot
AI
Mar 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating log files with mode 0644 makes them readable by other users on the host, which can expose sensitive data if logs contain secrets/tokens. Consider using 0600 by default (or making the mode configurable) to reduce unintended disclosure.
| f, err := os.OpenFile(h.path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) | |
| f, err := os.OpenFile(h.path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Changed all log file OpenFile calls from 0644 to 0600 — both in the fileHook.Fire method and in NewCustomLogger (for vuls.log and the per-host log file).
Copilot
AI
Mar 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
f.Write(b) can legally perform a short write without returning an error, which would truncate log entries. Consider looping until all bytes are written (or use an io.Copy-style approach) and also consider capturing/returning Close() errors (currently ignored via defer f.Close()).
| func (h *fileHook) Fire(entry *logrus.Entry) error { | |
| f, err := os.OpenFile(h.path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) | |
| if err != nil { | |
| return err | |
| } | |
| defer f.Close() | |
| b, err := h.formatter.Format(entry) | |
| if err != nil { | |
| return err | |
| } | |
| _, err = f.Write(b) | |
| return err | |
| func (h *fileHook) Fire(entry *logrus.Entry) (retErr error) { | |
| f, err := os.OpenFile(h.path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) | |
| if err != nil { | |
| return err | |
| } | |
| defer func() { | |
| if cerr := f.Close(); cerr != nil && retErr == nil { | |
| retErr = cerr | |
| } | |
| }() | |
| b, err := h.formatter.Format(entry) | |
| if err != nil { | |
| return err | |
| } | |
| total := 0 | |
| for total < len(b) { | |
| n, werr := f.Write(b[total:]) | |
| if werr != nil { | |
| return werr | |
| } | |
| if n == 0 { | |
| return io.ErrShortWrite | |
| } | |
| total += n | |
| } | |
| return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not fixing. Per the io.Writer contract, a short write must return a non-nil error (Write must return a non-nil error if it returns n < len(p)). The *os.File.Write implementation follows this contract. Adding a write loop would be dead code since any short write already surfaces as an error. Similarly, defer f.Close() already handles close — capturing the close error in a named return would add complexity with no practical benefit for a log hook.
Copilot
AI
Mar 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The os.OpenFile call opens a file descriptor that is never closed (the returned *os.File is discarded). This can leak a FD per logger initialization. Use os.OpenFile to get f, close it (or use os.OpenFile inside a small scope and defer f.Close()), or replace this check with an operation that doesn't allocate an FD (e.g., attempt to open with proper close, or rely on the hook's Fire() to surface errors).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. The os.OpenFile at line 119 (in NewCustomLogger) was only used to verify the file could be created, but the returned *os.File was discarded without closing. Fixed: renamed to checkFile and added checkFile.Close() immediately after the check succeeds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
\"/dev/null\"will fail on non-Unix platforms. Useos.DevNullfor a portable sink path.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Changed to
os.DevNullfor cross-platform portability.