No terminal window on windows
This commit is contained in:
105
internal/logging/logging.go
Normal file
105
internal/logging/logging.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"luxtools-client/internal/config"
|
||||
)
|
||||
|
||||
const (
|
||||
logBufferMaxBytes = 256 * 1024
|
||||
logFileMaxBytes = 5 * 1024 * 1024
|
||||
)
|
||||
|
||||
// Buffer stores recent log output for control pages.
|
||||
type Buffer struct {
|
||||
mu sync.Mutex
|
||||
buf []byte
|
||||
max int
|
||||
}
|
||||
|
||||
func newBuffer(max int) *Buffer {
|
||||
return &Buffer{max: max}
|
||||
}
|
||||
|
||||
func (b *Buffer) Write(p []byte) (int, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if len(p) > b.max {
|
||||
p = p[len(p)-b.max:]
|
||||
}
|
||||
b.buf = append(b.buf, p...)
|
||||
if len(b.buf) > b.max {
|
||||
b.buf = b.buf[len(b.buf)-b.max:]
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Bytes returns a copy of the buffered log content.
|
||||
func (b *Buffer) Bytes() []byte {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if len(b.buf) == 0 {
|
||||
return nil
|
||||
}
|
||||
copyBuf := make([]byte, len(b.buf))
|
||||
copy(copyBuf, b.buf)
|
||||
return copyBuf
|
||||
}
|
||||
|
||||
func logFilePath() (string, error) {
|
||||
configPath, err := config.ConfigPath()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(filepath.Dir(configPath), "luxtools-client.log"), nil
|
||||
}
|
||||
|
||||
func prepareLogFile(path string, maxBytes int64) (*os.File, error) {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info, err := os.Stat(path); err == nil && info.Size() > maxBytes {
|
||||
_ = os.Remove(path + ".old")
|
||||
_ = os.Rename(path, path+".old")
|
||||
}
|
||||
return os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
||||
}
|
||||
|
||||
// Setup builds loggers, a buffer, and optional file logging.
|
||||
func Setup() (*log.Logger, *log.Logger, *Buffer, string, func()) {
|
||||
logStore := newBuffer(logBufferMaxBytes)
|
||||
logPath, logErr := logFilePath()
|
||||
var logFile *os.File
|
||||
if logErr == nil {
|
||||
if f, err := prepareLogFile(logPath, logFileMaxBytes); err == nil {
|
||||
logFile = f
|
||||
} else {
|
||||
logPath = ""
|
||||
}
|
||||
} else {
|
||||
logPath = ""
|
||||
}
|
||||
|
||||
infoWriters := []io.Writer{logStore, os.Stdout}
|
||||
errWriters := []io.Writer{logStore, os.Stderr}
|
||||
if logFile != nil {
|
||||
infoWriters = append(infoWriters, logFile)
|
||||
errWriters = append(errWriters, logFile)
|
||||
}
|
||||
|
||||
infoLog := log.New(io.MultiWriter(infoWriters...), "", log.LstdFlags)
|
||||
errLog := log.New(io.MultiWriter(errWriters...), "ERROR: ", log.LstdFlags)
|
||||
|
||||
cleanup := func() {}
|
||||
if logFile != nil {
|
||||
cleanup = func() { _ = logFile.Close() }
|
||||
}
|
||||
|
||||
return infoLog, errLog, logStore, logPath, cleanup
|
||||
}
|
||||
@@ -179,6 +179,32 @@ var settingsTemplate = template.Must(template.New("settings").Parse(`<!doctype h
|
||||
</html>
|
||||
`))
|
||||
|
||||
var logsTemplate = template.Must(template.New("logs").Parse(`<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>luxtools-client Logs</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; margin: 1.25rem; }
|
||||
code, pre { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
||||
pre { background: #f7f7f7; padding: 0.75rem; border: 1px solid #ddd; overflow: auto; max-height: 70vh; }
|
||||
.small { color: #666; font-size: 0.9rem; }
|
||||
button { padding: 0.35rem 0.7rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Logs</h1>
|
||||
<p class="small">Log file: <code>{{ .LogPath }}</code></p>
|
||||
<p class="small">Updated: <code>{{ .Updated }}</code></p>
|
||||
<p>
|
||||
<a href="/logs">View raw text</a>
|
||||
<button type="button" onclick="location.reload()">Refresh</button>
|
||||
</p>
|
||||
<pre>{{ .LogText }}</pre>
|
||||
</body>
|
||||
</html>
|
||||
`))
|
||||
|
||||
// RenderIndex renders the main index page.
|
||||
func RenderIndex(w io.Writer, data any) error {
|
||||
return indexTemplate.Execute(w, data)
|
||||
@@ -188,3 +214,8 @@ func RenderIndex(w io.Writer, data any) error {
|
||||
func RenderSettings(w io.Writer) error {
|
||||
return settingsTemplate.Execute(w, nil)
|
||||
}
|
||||
|
||||
// RenderLogs renders the logs control page.
|
||||
func RenderLogs(w io.Writer, data any) error {
|
||||
return logsTemplate.Execute(w, data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user