157 lines
3.4 KiB
Go
157 lines
3.4 KiB
Go
package logger
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Log writes a log entry to the appropriate log file
|
|
func (l *Logger) Log(args LogItemArgs) error {
|
|
|
|
// Protect concurrent use
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
// Use current time if not provided
|
|
timestamp := time.Now()
|
|
if args.Timestamp != nil {
|
|
timestamp = *args.Timestamp
|
|
}
|
|
|
|
// Format category (max 10 chars, ASCII only)
|
|
category := formatName(args.Category)
|
|
if len(category) > 10 {
|
|
return fmt.Errorf("category cannot be longer than 10 chars")
|
|
}
|
|
category = expandString(category, 10, ' ')
|
|
|
|
// Clean up the message
|
|
message := strings.TrimSpace(dedent(args.Message))
|
|
|
|
// Determine log file path based on date and hour
|
|
logFilePath := filepath.Join(l.Path, fmt.Sprintf("%s.log", formatDayHour(timestamp)))
|
|
|
|
// Create log file if it doesn't exist
|
|
if _, err := os.Stat(logFilePath); os.IsNotExist(err) {
|
|
if err := os.WriteFile(logFilePath, []byte{}, 0644); err != nil {
|
|
return err
|
|
}
|
|
l.LastLogTime = 0 // Make sure we put time again
|
|
}
|
|
|
|
// Open file for appending
|
|
f, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
closeErr := f.Close()
|
|
if err == nil && closeErr != nil {
|
|
err = closeErr
|
|
}
|
|
}()
|
|
|
|
var content strings.Builder
|
|
|
|
// Add timestamp if we're in a new second
|
|
currentUnix := timestamp.Unix()
|
|
if currentUnix > l.LastLogTime {
|
|
// If not the first entry in the file, add an extra newline for separation
|
|
if l.LastLogTime != 0 {
|
|
content.WriteString("\n")
|
|
}
|
|
content.WriteString(fmt.Sprintf("%s\n", timestamp.Format("15:04:05")))
|
|
l.LastLogTime = currentUnix
|
|
}
|
|
|
|
// Format log lines
|
|
errorPrefix := " " // Default for stdout
|
|
if args.LogType == LogTypeError {
|
|
errorPrefix = "E"
|
|
}
|
|
|
|
lines := strings.Split(message, "\n")
|
|
for i, line := range lines {
|
|
if i == 0 {
|
|
content.WriteString(fmt.Sprintf("%s %s - %s\n", errorPrefix, category, line))
|
|
} else {
|
|
content.WriteString(fmt.Sprintf("%s %s\n", errorPrefix, line))
|
|
}
|
|
}
|
|
|
|
// Write to file
|
|
// Don't trim the trailing newline to ensure proper line separation
|
|
_, err = f.WriteString(content.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Helper functions
|
|
func formatName(name string) string {
|
|
// Replace hyphens with underscores and remove non-alphanumeric chars
|
|
result := strings.Map(func(r rune) rune {
|
|
if r == '-' {
|
|
return '_'
|
|
}
|
|
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' {
|
|
return r
|
|
}
|
|
return '_'
|
|
}, name)
|
|
return result
|
|
}
|
|
|
|
func expandString(s string, length int, char byte) string {
|
|
if len(s) >= length {
|
|
return s
|
|
}
|
|
return s + strings.Repeat(string(char), length-len(s))
|
|
}
|
|
|
|
func dedent(s string) string {
|
|
lines := strings.Split(s, "\n")
|
|
if len(lines) <= 1 {
|
|
return s
|
|
}
|
|
|
|
// Find minimum indentation
|
|
minIndent := -1
|
|
for _, line := range lines[1:] {
|
|
trimmed := strings.TrimLeft(line, " \t")
|
|
if len(trimmed) == 0 {
|
|
continue // Skip empty lines
|
|
}
|
|
indent := len(line) - len(trimmed)
|
|
if minIndent == -1 || indent < minIndent {
|
|
minIndent = indent
|
|
}
|
|
}
|
|
|
|
// No indentation found
|
|
if minIndent <= 0 {
|
|
return s
|
|
}
|
|
|
|
// Remove common indentation
|
|
result := []string{lines[0]}
|
|
for _, line := range lines[1:] {
|
|
if len(line) > minIndent {
|
|
result = append(result, line[minIndent:])
|
|
} else {
|
|
result = append(result, line)
|
|
}
|
|
}
|
|
|
|
return strings.Join(result, "\n")
|
|
}
|
|
|
|
func formatDayHour(t time.Time) string {
|
|
return t.Format("2006-01-02-15")
|
|
}
|