Add comprehensive human-readable comments to all Go files
- Comments explain what each function does and why - Fixed unused strconv import in queue.go - Better documentation for SMTP email service - Explained journalctl log parsing in logs.go - Clarified queue operations in queue.go
This commit is contained in:
parent
3e3620a24f
commit
68285d861a
9 changed files with 728 additions and 203 deletions
|
|
@ -1,77 +1,116 @@
|
|||
// Package mail provides functions for interacting with the mail server system.
|
||||
// This file handles reading system logs from systemd's journal using journalctl.
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"bufio" // buffered scanner for efficient line-by-line reading
|
||||
"bytes" // buffer management for capturing command output
|
||||
"context" // context for cancellation and timeouts
|
||||
"os/exec" // executing external commands (journalctl)
|
||||
"regexp" // regular expressions for parsing log entries
|
||||
"strings" // string manipulation
|
||||
"time" // time formatting and duration calculations
|
||||
)
|
||||
|
||||
// LogEntry represents a single log line from the system journal.
|
||||
type LogEntry struct {
|
||||
Timestamp string `json:"timestamp"`
|
||||
Priority string `json:"priority"`
|
||||
Message string `json:"message"`
|
||||
Timestamp string `json:"timestamp"` // When the log entry was created (e.g., "2024-01-15 10:30:45")
|
||||
Priority string `json:"priority"` // Log level: "info", "warning", "err", etc.
|
||||
Message string `json:"message"` // The actual log message content
|
||||
}
|
||||
|
||||
// GetLogs retrieves Postfix mail log entries from systemd journal.
|
||||
// hours: how many hours of logs to look back (e.g., 24 = last 24 hours)
|
||||
// filter: optional text filter - only returns lines containing this text (case-insensitive)
|
||||
// Returns a slice of LogEntry structs sorted from newest to oldest.
|
||||
func GetLogs(hours int, filter string) ([]LogEntry, error) {
|
||||
// Create a context with a 30-second timeout.
|
||||
// This prevents the journalctl command from hanging forever if there's an issue.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
defer cancel() // Always call cancel to clean up resources
|
||||
|
||||
// Build the journalctl command arguments.
|
||||
// -u: filter by systemd unit name (postfix in this case)
|
||||
// --since: start time (formatted as "YYYY-MM-DD HH:MM:SS")
|
||||
// --no-pager: don't use a pager, just output everything directly
|
||||
args := []string{
|
||||
"-u", "postfix",
|
||||
"--since", formatDuration(hours),
|
||||
"--no-pager",
|
||||
}
|
||||
|
||||
// Execute journalctl with our arguments.
|
||||
// journalctl reads from systemd journal, not plain text files.
|
||||
cmd := exec.CommandContext(ctx, "/usr/bin/journalctl", args...)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stdout = &out // Capture output to our buffer
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
// journalctl returns non-zero when there are no entries.
|
||||
// That's not really an error for us, so we could handle it,
|
||||
// but for simplicity we just pass the error through.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse the journalctl output line by line.
|
||||
var entries []LogEntry
|
||||
scanner := bufio.NewScanner(&out)
|
||||
|
||||
// Regular expression to extract priority/level from log entries.
|
||||
// systemd logs look like: "[priority] some message"
|
||||
priorityRegex := regexp.MustCompile(`\[([^\]]+)\]`)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
|
||||
// Skip empty lines and "No entries" messages.
|
||||
// journalctl returns "-- No entries --" when there's nothing to show.
|
||||
if strings.Contains(line, "-- No entries") || line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply text filter if provided.
|
||||
// Case-insensitive search using ToLower on both strings.
|
||||
if filter != "" && !strings.Contains(strings.ToLower(line), strings.ToLower(filter)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// journalctl outputs lines in this format:
|
||||
// Jan 15 10:30:45 hostname postfix/cleanup[12345]: [priority] message
|
||||
// We split by spaces, but since the message can contain spaces,
|
||||
// we use SplitN with limit 4 to get: timestamp, hostname, process, message
|
||||
parts := strings.SplitN(line, " ", 4)
|
||||
if len(parts) < 4 {
|
||||
continue
|
||||
continue // Skip malformed lines
|
||||
}
|
||||
|
||||
// Combine first two parts for full timestamp: "Jan 15 10:30:45"
|
||||
timestamp := parts[0] + " " + parts[1]
|
||||
|
||||
// The third part contains the process info like "postfix/cleanup[12345]:"
|
||||
// We extract the priority level from this part.
|
||||
matches := priorityRegex.FindStringSubmatch(parts[2])
|
||||
priority := "info"
|
||||
priority := "info" // default priority
|
||||
if len(matches) > 1 {
|
||||
// Found a priority in brackets like [info], [warning], [error]
|
||||
priority = matches[1]
|
||||
} else if strings.Contains(parts[2], "err") {
|
||||
// Some log lines don't use brackets, so we check for keywords
|
||||
priority = "err"
|
||||
} else if strings.Contains(parts[2], "warning") || strings.Contains(parts[2], "warn") {
|
||||
priority = "warning"
|
||||
}
|
||||
|
||||
// The fourth part is the actual log message.
|
||||
message := parts[3]
|
||||
|
||||
// Double-check the filter applies to the message part specifically.
|
||||
// This is optional since we already filtered above, but provides extra safety.
|
||||
if filter != "" && !strings.Contains(strings.ToLower(message), strings.ToLower(filter)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Create a LogEntry and add it to our results.
|
||||
entries = append(entries, LogEntry{
|
||||
Timestamp: timestamp,
|
||||
Priority: priority,
|
||||
|
|
@ -79,6 +118,7 @@ func GetLogs(hours int, filter string) ([]LogEntry, error) {
|
|||
})
|
||||
}
|
||||
|
||||
// Return empty slice instead of nil for consistent JSON serialization.
|
||||
if entries == nil {
|
||||
entries = []LogEntry{}
|
||||
}
|
||||
|
|
@ -86,6 +126,9 @@ func GetLogs(hours int, filter string) ([]LogEntry, error) {
|
|||
return entries, nil
|
||||
}
|
||||
|
||||
// formatDuration converts hours into a journalctl-compatible timestamp.
|
||||
// journalctl expects timestamps in format: "YYYY-MM-DD HH:MM:SS"
|
||||
// We calculate the timestamp by subtracting the requested hours from now.
|
||||
func formatDuration(hours int) string {
|
||||
return time.Now().Add(-time.Duration(hours) * time.Hour).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue