summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/log/filelogger.go
blob: e3ff42b526fa9453e0e71ba1fd68065ff2c788a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package log

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"sync"
	"time"
)

type FileLogger struct {
	file       *os.File
	writer     *bufio.Writer
	logPath    string
	mu         sync.Mutex
	stopChan   chan struct{}
	doneChan   chan struct{}
	passwordRe *regexp.Regexp
}

func NewFileLogger() (*FileLogger, error) {
	timestamp := time.Now().Unix()

	// Use DANKINSTALL_LOG_DIR if set, otherwise fall back to /tmp.
	logDir := os.Getenv("DANKINSTALL_LOG_DIR")
	if logDir == "" {
		logDir = "/tmp"
	}
	if err := os.MkdirAll(logDir, 0o755); err != nil {
		return nil, fmt.Errorf("failed to create log directory: %w", err)
	}
	logPath := filepath.Join(logDir, fmt.Sprintf("dankinstall-%d.log", timestamp))

	file, err := os.Create(logPath)
	if err != nil {
		return nil, fmt.Errorf("failed to create log file: %w", err)
	}

	passwordRe := regexp.MustCompile(`(?i)(password[:\s=]+)[^\s]+`)

	logger := &FileLogger{
		file:       file,
		writer:     bufio.NewWriter(file),
		logPath:    logPath,
		stopChan:   make(chan struct{}),
		doneChan:   make(chan struct{}),
		passwordRe: passwordRe,
	}

	header := fmt.Sprintf("=== DankInstall Log ===\nStarted: %s\n\n", time.Now().Format(time.RFC3339))
	logger.writeToFile(header)

	return logger, nil
}

func (l *FileLogger) GetLogPath() string {
	return l.logPath
}

func (l *FileLogger) redactPassword(message string) string {
	redacted := l.passwordRe.ReplaceAllString(message, "${1}[REDACTED]")

	redacted = regexp.MustCompile(`echo\s+'[^']+'`).ReplaceAllString(redacted, "echo '[REDACTED]'")

	return redacted
}

func (l *FileLogger) writeToFile(message string) {
	l.mu.Lock()
	defer l.mu.Unlock()

	redacted := l.redactPassword(message)
	timestamp := time.Now().Format("15:04:05.000")

	fmt.Fprintf(l.writer, "[%s] %s\n", timestamp, redacted)
	l.writer.Flush()
}

func (l *FileLogger) StartListening(logChan <-chan string) {
	go func() {
		defer close(l.doneChan)
		for {
			select {
			case msg, ok := <-logChan:
				if !ok {
					return
				}
				l.writeToFile(msg)
			case <-l.stopChan:
				return
			}
		}
	}()
}

func (l *FileLogger) Close() error {
	close(l.stopChan)
	<-l.doneChan

	l.mu.Lock()
	defer l.mu.Unlock()

	footer := fmt.Sprintf("\n=== DankInstall Log End ===\nCompleted: %s\n", time.Now().Format(time.RFC3339))
	l.writer.WriteString(footer) //nolint:errcheck
	l.writer.Flush()

	if err := l.file.Sync(); err != nil {
		return err
	}

	return l.file.Close()
}