diff --git a/pkg/notify.go b/pkg/notify.go index 67da9b9..271f22b 100644 --- a/pkg/notify.go +++ b/pkg/notify.go @@ -35,7 +35,8 @@ func NotifyOwnError(e error, r slog.Record, msTeamsHook, proxy string) { } err := gmt.Send(hostname, details, msTeamsHook, proxy) if err != nil { - slog.Error("Error sending to Teams", "error", err.Error()) + // keep it warn to prevent infinite loop from the global handler of slog + slog.Warn("Error sending to Teams", "error", err.Error()) } else { slog.Info("Successfully sent own error to MS Teams") } @@ -49,46 +50,48 @@ func Notify(result *ScanResult, f Flags, version string) { Message: version, }, { - Label: "File Path", + Label: "File", Message: result.FilePath, }, { - Label: "Running Every", - Message: fmt.Sprintf("%d secs", f.Every), - }, - { - Label: "Match Pattern", + Label: "Match", Message: f.Match, }, { - Label: "Ignore Pattern", + Label: "Ignore", Message: f.Ignore, }, { - Label: "First Line", - Message: Truncate(result.FirstLine, TruncateMax), - }, - { - Label: "Mid Lines", - Message: result.PreviewLine, + Label: "Lines", + Message: fmt.Sprintf( + "%s\n\r%s\n\r%s", + Truncate(result.FirstLine, TruncateMax), + ReduceToNLines(result.PreviewLine, 3), + Truncate(result.LastLine, TruncateMax), + ), }, { - Label: "Last Line", - Message: Truncate(result.LastLine, TruncateMax), + Label: "Settings", + Message: fmt.Sprintf( + "min (%d), every (%d secs), max streak (%d)", + f.Min, + f.Every, + f.Streak, + ), }, { - Label: "Details", + Label: "Scan Details", Message: fmt.Sprintf( - "Min Threshold: %d, Lines Read: %d\n\rMatches Found: %d, Ratio %.2f%%", - f.Min, - result.LinesRead, - result.ErrorCount, + "lines read (%s), %.2f%% errors (%s), scans til date (%s)", + NumberToK(result.LinesRead), result.ErrorPercent, + NumberToK(result.ErrorCount), + NumberToK(result.ScanCount), ), }, { Label: "Streaks", - Message: StreakSymbols(result.Streak, f.Streak, f.Min) + "\n\r" + fmt.Sprintf("Last %d failed. Scan counter: %d", f.Streak, result.ScanCount), + Message: StreakSymbols(result.Streak, f.Streak, f.Min), }, } if result.FirstDate != "" || result.LastDate != "" { @@ -96,20 +99,20 @@ func Notify(result *ScanResult, f Flags, version string) { if result.FirstDate != "" && result.LastDate != "" { firstDate, err := time.Parse("2006-01-02 15:04:05", result.FirstDate) if err != nil { - duration = "X" + duration = "" } else { lastDate, err := time.Parse("2006-01-02 15:04:05", result.LastDate) if err == nil { - duration = lastDate.Sub(firstDate).String() + duration = fmt.Sprintf("(%s)", lastDate.Sub(firstDate).String()) } else { - duration = "X" + duration = "" } } } details = append(details, gmt.Details{ Label: "Range", - Message: fmt.Sprintf("%s to %s (Duration: %s)", result.FirstDate, result.LastDate, duration), + Message: fmt.Sprintf("%s to %s %s", result.FirstDate, result.LastDate, duration), }) } @@ -129,7 +132,8 @@ func Notify(result *ScanResult, f Flags, version string) { err := gmt.Send(hostname, details, f.MSTeamsHook, f.Proxy) if err != nil { - slog.Error("Error sending to Teams", "error", err.Error()) + // keep it warn to prevent infinite loop from the global handler of slog + slog.Warn("Error sending to Teams", "error", err.Error()) } else { slog.Info("Successfully sent to MS Teams") } diff --git a/pkg/strings.go b/pkg/strings.go index 5c3033b..f63c833 100644 --- a/pkg/strings.go +++ b/pkg/strings.go @@ -4,6 +4,7 @@ import ( "crypto/sha256" "encoding/hex" "log/slog" + "strconv" "strings" "sync" @@ -87,6 +88,26 @@ func StreakSymbols(arr []int, length int, minimum int) string { for i := len(symbols); i < DisplayableStreakNumber(length); i++ { symbols = append([]string{"□"}, symbols...) } + // if last is ✕ then replace with ✖(bold) + if symbols[len(symbols)-1] == "✕" { + symbols[len(symbols)-1] = "✖" + } + + return strings.Join(symbols, "") +} + +func NumberToK(num int) string { + if num >= 1000 { + return strconv.FormatFloat(float64(num)/1000, 'f', 1, 64) + "K" + } + return strconv.Itoa(num) +} - return strings.Join(symbols, " ") +// reduce to n lines +func ReduceToNLines(s string, n int) string { + lines := strings.Split(s, "\n") + if len(lines) <= n { + return s + } + return strings.Join(lines[:n], "\n") } diff --git a/pkg/strings_test.go b/pkg/strings_test.go new file mode 100644 index 0000000..18dc904 --- /dev/null +++ b/pkg/strings_test.go @@ -0,0 +1,34 @@ +package pkg + +import ( + "strconv" + "testing" +) + +func TestNumberToK(t *testing.T) { + tests := []struct { + input int + expected string + }{ + {999, "999"}, // Less than 1000 + {1000, "1.0K"}, // Exactly 1000 + {1500, "1.5K"}, // Simple thousands + {25000, "25.0K"}, // Larger thousands + {1000000, "1000.0K"}, // Very large numbers + {0, "0"}, // Edge case: zero + {-500, "-500"}, // Negative numbers (no conversion) + {-1500, "-1500"}, // Negative thousand (not supported) + } + + for _, test := range tests { + t.Run( + "Input: "+strconv.Itoa(test.input), + func(t *testing.T) { + result := NumberToK(test.input) + if result != test.expected { + t.Errorf("ConvertToK(%d) = %s; expected %s", test.input, result, test.expected) + } + }, + ) + } +} diff --git a/pkg/watcher.go b/pkg/watcher.go index f5210f2..f234049 100644 --- a/pkg/watcher.go +++ b/pkg/watcher.go @@ -117,6 +117,7 @@ func (w *Watcher) Scan() (*ScanResult, error) { currentLineNum := 1 linesRead := 0 bytesRead := w.lastFileSize + isFirstScan := w.getScanCount() == 0 for scanner.Scan() { line := scanner.Bytes() @@ -128,6 +129,10 @@ func (w *Watcher) Scan() (*ScanResult, error) { linesRead = -linesRead } + if isFirstScan { + continue + } + if w.ignorePattern != "" && regIgnore.Match(line) { continue } diff --git a/pkg/watcher_test.go b/pkg/watcher_test.go index 598fe06..f66c15d 100644 --- a/pkg/watcher_test.go +++ b/pkg/watcher_test.go @@ -35,6 +35,7 @@ func TestNewWatcher(t *testing.T) { caches := make(map[string]*cache.Cache) caches[filePath] = cache.New(cache.NoExpiration, cache.NoExpiration) watcher, err := NewWatcher(filePath, f, caches[filePath]) + watcher.incrementScanCount() assert.NoError(t, err) assert.NotNil(t, watcher) @@ -62,6 +63,7 @@ error:1` caches := make(map[string]*cache.Cache) caches[filePath] = cache.New(cache.NoExpiration, cache.NoExpiration) watcher, err := NewWatcher(filePath, f, caches[filePath]) + watcher.incrementScanCount() assert.NoError(t, err) defer watcher.Close() @@ -91,6 +93,7 @@ line2` caches := make(map[string]*cache.Cache) caches[filePath] = cache.New(cache.NoExpiration, cache.NoExpiration) watcher, err := NewWatcher(filePath, f, caches[filePath]) + watcher.incrementScanCount() assert.NoError(t, err) defer watcher.Close() @@ -134,6 +137,7 @@ error:1` caches := make(map[string]*cache.Cache) caches[filePath] = cache.New(cache.NoExpiration, cache.NoExpiration) watcher, err := NewWatcher(filePath, f, caches[filePath]) + watcher.incrementScanCount() if err != nil { b.Fatal(err) } @@ -160,6 +164,7 @@ func BenchmarkLoadAndSaveState(b *testing.B) { caches := make(map[string]*cache.Cache) caches[filePath] = cache.New(cache.NoExpiration, cache.NoExpiration) watcher, err := NewWatcher(filePath, f, caches[filePath]) + watcher.incrementScanCount() if err != nil { b.Fatal(err) } @@ -171,6 +176,7 @@ func BenchmarkLoadAndSaveState(b *testing.B) { caches := make(map[string]*cache.Cache) caches[filePath] = cache.New(cache.NoExpiration, cache.NoExpiration) _, err := NewWatcher(filePath, f, caches[filePath]) + watcher.incrementScanCount() if err != nil { b.Fatal(err) } @@ -198,6 +204,7 @@ line2` caches := make(map[string]*cache.Cache) caches[filePath] = cache.New(cache.NoExpiration, cache.NoExpiration) watcher, err := NewWatcher(filePath, f, caches[filePath]) + watcher.incrementScanCount() if err != nil { b.Fatal(err) }