feat: add configurable data directory and file-based logging
Introduce the `WARPBOX_DATA_DIR` environment variable to define where runtime data is stored. This directory will house uploaded files, the bbolt metadata database, and application logs. Changes include: - Added `WARPBOX_DATA_DIR` to configuration, defaulting to `./data`. - Implemented a custom logging package that writes JSONL logs to the data directory. - Updated `.gitignore` and `.env.example` to support the new data directory. - Documented the runtime data structure in `README.md`. - Updated the frontend upload script to handle form submission and display results.
This commit is contained in:
105
backend/libs/logging/logger.go
Normal file
105
backend/libs/logging/logger.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Closer func() error
|
||||
|
||||
type jsonLineHandler struct {
|
||||
mu *sync.Mutex
|
||||
out io.Writer
|
||||
attrs []slog.Attr
|
||||
}
|
||||
|
||||
func New(dataDir string) (*slog.Logger, Closer, error) {
|
||||
logDir := filepath.Join(dataDir, "logs")
|
||||
if err := os.MkdirAll(logDir, 0o755); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
path := filepath.Join(logDir, time.Now().Format("2006-01-02")+".log")
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
handler := &jsonLineHandler{
|
||||
mu: &sync.Mutex{},
|
||||
out: io.MultiWriter(os.Stdout, file),
|
||||
}
|
||||
|
||||
return slog.New(handler), file.Close, nil
|
||||
}
|
||||
|
||||
func (h *jsonLineHandler) Enabled(context.Context, slog.Level) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *jsonLineHandler) Handle(_ context.Context, record slog.Record) error {
|
||||
entry := map[string]any{
|
||||
"date": record.Time.Format("2006-01-02"),
|
||||
"time": record.Time.Format("15:04:05"),
|
||||
"source": "app",
|
||||
"code": 0,
|
||||
"severity": severity(record.Level),
|
||||
"log": record.Message,
|
||||
}
|
||||
|
||||
for _, attr := range h.attrs {
|
||||
applyAttr(entry, attr)
|
||||
}
|
||||
record.Attrs(func(attr slog.Attr) bool {
|
||||
applyAttr(entry, attr)
|
||||
return true
|
||||
})
|
||||
|
||||
line, err := json.Marshal(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
_, err = h.out.Write(append(line, '\n'))
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *jsonLineHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
next := &jsonLineHandler{
|
||||
mu: h.mu,
|
||||
out: h.out,
|
||||
attrs: append([]slog.Attr{}, h.attrs...),
|
||||
}
|
||||
next.attrs = append(next.attrs, attrs...)
|
||||
return next
|
||||
}
|
||||
|
||||
func (h *jsonLineHandler) WithGroup(string) slog.Handler {
|
||||
return h
|
||||
}
|
||||
|
||||
func applyAttr(entry map[string]any, attr slog.Attr) {
|
||||
attr.Value = attr.Value.Resolve()
|
||||
entry[attr.Key] = attr.Value.Any()
|
||||
}
|
||||
|
||||
func severity(level slog.Level) string {
|
||||
switch {
|
||||
case level >= slog.LevelError:
|
||||
return "high"
|
||||
case level >= slog.LevelWarn:
|
||||
return "medium"
|
||||
case level <= slog.LevelDebug:
|
||||
return "low"
|
||||
default:
|
||||
return "info"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user