From f39f5b1b16f81901210b2f9a77af9070206bb129 Mon Sep 17 00:00:00 2001 From: Esa Kataja Date: Sun, 21 Jun 2026 17:08:23 +0300 Subject: [PATCH] add: SQLite-backed structured logging with retention Replace JSON/file logging with a logs.db (WAL) store. Thread the logger through the encoder and metadata client for debug instrumentation of every ffmpeg/ffprobe/opusenc invocation and OMDb/TVmaze request. API keys are redacted before request URLs are logged. Retention defaults to 7 days, overridable via log_retention_days in config. --- cmd/videnc/main.go | 10 ++- go.mod | 2 + go.sum | 2 + internal/config/config.go | 3 + internal/encoder/encoder.go | 131 +++++++++++++++++++------------- internal/logger/logger.go | 138 ++++++++++++++++++++-------------- internal/metadata/metadata.go | 50 ++++++++++-- pkg/types/types.go | 19 +---- 8 files changed, 221 insertions(+), 134 deletions(-) diff --git a/cmd/videnc/main.go b/cmd/videnc/main.go index c3b5381..6861660 100644 --- a/cmd/videnc/main.go +++ b/cmd/videnc/main.go @@ -46,21 +46,21 @@ func main() { os.Exit(1) } - log, err := logger.New(".") + log, err := logger.New(".", cfg.LogRetentionDays) if err != nil { fmt.Fprintf(os.Stderr, "Failed to initialize logger: %v\n", err) os.Exit(1) } defer log.Close() - enc := encoder.New() + enc := encoder.New(log) if err := enc.CheckDeps(); err != nil { log.Error("Dependency check failed", err.Error()) fmt.Fprintf(os.Stderr, "Dependency check failed: %v\n", err) os.Exit(1) } - metaClient := metadata.NewClient(cfg.OMDBAPIKey) + metaClient := metadata.NewClient(cfg.OMDBAPIKey, log) ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer cancel() @@ -128,11 +128,15 @@ func processFile(ctx context.Context, inputPath string, cfg *types.Config, enc * meta, err = metaClient.FetchSeriesMetadata(ctx, tvmazeID, season, episode) if err != nil { log.ErrorFile(inputPath, "Fetching series metadata", err.Error()) + } else if meta != nil { + log.Info(fmt.Sprintf("TVmaze hit: %s S%sE%s - %s", meta.Collection, meta.Season, meta.Episode, meta.Title)) } } else if imdbID != "" { meta, err = metaClient.FetchMovieMetadata(ctx, imdbID) if err != nil { log.ErrorFile(inputPath, "Fetching movie metadata", err.Error()) + } else if meta != nil { + log.Info(fmt.Sprintf("OMDb hit: %s (%s)", meta.Title, meta.IMDBID)) } } diff --git a/go.mod b/go.mod index ab86294..aca3e44 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,5 @@ module videnc-vibe go 1.26.1 require gopkg.in/yaml.v3 v3.0.1 + +require github.com/mattn/go-sqlite3 v1.14.44 // indirect diff --git a/go.sum b/go.sum index a62c313..076290f 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8= +github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/config/config.go b/internal/config/config.go index 89f3ec5..4c67297 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -76,6 +76,9 @@ func Load(configPath string) (*types.Config, error) { if cfg.Encoding.TVRip.Preset == 0 { cfg.Encoding.TVRip.Preset = 2 } + if cfg.LogRetentionDays == 0 { + cfg.LogRetentionDays = 7 + } return &cfg, nil } diff --git a/internal/encoder/encoder.go b/internal/encoder/encoder.go index 0589c72..27e6bf0 100644 --- a/internal/encoder/encoder.go +++ b/internal/encoder/encoder.go @@ -11,6 +11,7 @@ import ( "strconv" "strings" + "videnc-vibe/internal/logger" "videnc-vibe/pkg/types" ) @@ -20,6 +21,7 @@ type Encoder struct { ffmpegPath string ffprobePath string opusencPath string + log *logger.Logger } type StreamInfo struct { @@ -43,9 +45,48 @@ type StreamMetadata struct { Title string `json:"title"` } +func New(log *logger.Logger) *Encoder { + return &Encoder{ + ffmpegPath: "ffmpeg", + ffprobePath: "ffprobe", + opusencPath: "opusenc", + log: log, + } +} + +// runCmd executes the command and emits a debug entry containing the full +// command line and its combined output. file is the source file the command +// is acting on (may be empty). +func (e *Encoder) runCmd(ctx context.Context, label, file, name string, args []string) ([]byte, error) { + cmd := exec.CommandContext(ctx, name, args...) + out, err := cmd.CombinedOutput() + extra, _ := json.Marshal(struct { + Cmd string `json:"cmd"` + Output string `json:"output"` + }{ + Cmd: name + " " + strings.Join(args, " "), + Output: string(out), + }) + e.log.Debug(label, file, string(extra)) + return out, err +} + +func (e *Encoder) CheckDeps() error { + if _, err := exec.LookPath(e.ffmpegPath); err != nil { + return fmt.Errorf("ffmpeg not found") + } + if _, err := exec.LookPath(e.ffprobePath); err != nil { + return fmt.Errorf("ffprobe not found") + } + if _, err := exec.LookPath(e.opusencPath); err != nil { + return fmt.Errorf("opusenc not found") + } + return nil +} + func (e *Encoder) GetStreamLanguages(ctx context.Context, path string) ([]StreamMetadata, error) { - cmd := exec.CommandContext(ctx, e.ffprobePath, "-v", "error", "-show_streams", "-print_format", "json", path) - output, err := cmd.CombinedOutput() + args := []string{"-v", "error", "-show_streams", "-print_format", "json", path} + output, err := e.runCmd(ctx, "ffprobe stream languages", path, e.ffprobePath, args) if err != nil { return nil, fmt.Errorf("ffprobe error: %w", err) } @@ -79,40 +120,18 @@ func (e *Encoder) GetStreamLanguages(ctx context.Context, path string) ([]Stream }) } - fmt.Printf("DEBUG stream languages: %+v\n", streams) + streamsJSON, _ := json.Marshal(streams) + e.log.Debug("parsed stream languages", path, string(streamsJSON)) return streams, nil } -func New() *Encoder { - return &Encoder{ - ffmpegPath: "ffmpeg", - ffprobePath: "ffprobe", - opusencPath: "opusenc", - } -} - -func (e *Encoder) CheckDeps() error { - if _, err := exec.LookPath(e.ffmpegPath); err != nil { - return fmt.Errorf("ffmpeg not found") - } - if _, err := exec.LookPath(e.ffprobePath); err != nil { - return fmt.Errorf("ffprobe not found") - } - if _, err := exec.LookPath(e.opusencPath); err != nil { - return fmt.Errorf("opusenc not found") - } - return nil -} - func (e *Encoder) GetMediaInfo(ctx context.Context, path string) (width, height int, interlaced bool, err error) { - cmd := exec.CommandContext(ctx, e.ffprobePath, "-v", "error", "-show_streams", "-print_format", "json", path) - output, err := cmd.CombinedOutput() + probeArgs := []string{"-v", "error", "-show_streams", "-print_format", "json", path} + output, err := e.runCmd(ctx, "ffprobe media info", path, e.ffprobePath, probeArgs) if err != nil { return 0, 0, false, fmt.Errorf("ffprobe error: %w", err) } - fmt.Printf("DEBUG ffprobe output: %s\n", string(output)) - var result ProbeResult if err := json.Unmarshal(output, &result); err != nil { return 0, 0, false, fmt.Errorf("parsing ffprobe output: %w", err) @@ -124,7 +143,10 @@ func (e *Encoder) GetMediaInfo(ctx context.Context, path string) (width, height width = stream.Width height = stream.Height foundVideo = true - fmt.Printf("DEBUG detected video: %dx%d, codec=%s, SAR=%s\n", width, height, stream.CodecName, stream.SampleAspectRatio) + info, _ := json.Marshal(map[string]interface{}{ + "width": width, "height": height, "codec": stream.CodecName, "sar": stream.SampleAspectRatio, + }) + e.log.Debug("detected video stream", path, string(info)) break } } @@ -132,7 +154,7 @@ func (e *Encoder) GetMediaInfo(ctx context.Context, path string) (width, height return 0, 0, false, fmt.Errorf("no video stream found") } - cmd = exec.CommandContext(ctx, e.ffmpegPath, + idetArgs := []string{ "-hide_banner", "-nostats", "-i", path, @@ -140,11 +162,12 @@ func (e *Encoder) GetMediaInfo(ctx context.Context, path string) (width, height "-frames:v", "400", "-an", "-sn", "-f", "null", "-", - ) - idetOut, _ := cmd.CombinedOutput() + } + idetOut, _ := e.runCmd(ctx, "ffmpeg idet", path, e.ffmpegPath, idetArgs) interlaced = detectInterlaced(string(idetOut)) - fmt.Printf("DEBUG interlaced: %v\n", interlaced) + info, _ := json.Marshal(map[string]bool{"interlaced": interlaced}) + e.log.Debug("interlace detection", path, string(info)) return width, height, interlaced, nil } @@ -170,14 +193,12 @@ func detectInterlaced(idetOutput string) bool { } func (e *Encoder) calculateZscaleWidth(ctx context.Context, path string, originalHeight int) (string, int, error) { - cmd := exec.CommandContext(ctx, e.ffprobePath, "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height,sample_aspect_ratio", "-print_format", "json", path) - output, err := cmd.CombinedOutput() + args := []string{"-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height,sample_aspect_ratio", "-print_format", "json", path} + output, err := e.runCmd(ctx, "ffprobe zscale info", path, e.ffprobePath, args) if err != nil { return "", 0, fmt.Errorf("ffprobe error: %w", err) } - fmt.Printf("DEBUG ffprobe for zscale: %s\n", string(output)) - var result ProbeResult if err := json.Unmarshal(output, &result); err != nil { return "", 0, fmt.Errorf("parsing ffprobe: %w", err) @@ -190,9 +211,7 @@ func (e *Encoder) calculateZscaleWidth(ctx context.Context, path string, origina stream := result.Streams[0] width := stream.Width height := stream.Height - sar := stream.SampleAspectRatio - fmt.Printf("DEBUG stream: width=%d, height=%d, sar=%s\n", width, height, sar) newWidth := width @@ -212,7 +231,11 @@ func (e *Encoder) calculateZscaleWidth(ctx context.Context, path string, origina // Round to nearest, then snap down to even (mod-2 widths preferred by AV1/H.264). newWidth = ((width*num + den/2) / den) &^ 1 - fmt.Printf("DEBUG calculated new width: %d (sar=%s)\n", newWidth, sar) + + info, _ := json.Marshal(map[string]interface{}{ + "width": width, "height": height, "sar": sar, "new_width": newWidth, + }) + e.log.Debug("zscale calculation", path, string(info)) if newWidth == width { return "", newWidth, nil @@ -263,11 +286,14 @@ func (e *Encoder) extractAudio(ctx context.Context, input, workDir string, strea wavs := make([]string, 0, len(audio)) for srcAudioIndex := range audio { wavPath := filepath.Join(workDir, fmt.Sprintf("audio.%d.wav", srcAudioIndex)) - cmd := exec.CommandContext(ctx, e.ffmpegPath, "-i", input, + args := []string{ + "-i", input, "-map", fmt.Sprintf("0:a:%d", srcAudioIndex), "-vn", "-c:a", "pcm_s16le", "-ar", "48000", - "-f", "wav", wavPath) - if out, err := cmd.CombinedOutput(); err != nil { + "-f", "wav", wavPath, + } + out, err := e.runCmd(ctx, "ffmpeg extract audio", input, e.ffmpegPath, args) + if err != nil { return wavs, fmt.Errorf("ffmpeg extract a:%d: %s %w", srcAudioIndex, out, err) } wavs = append(wavs, wavPath) @@ -280,8 +306,9 @@ func (e *Encoder) encodeOpus(ctx context.Context, wavs []string, workDir string) for _, wav := range wavs { base := filepath.Base(strings.Replace(wav, ".wav", ".opus", 1)) out := filepath.Join(workDir, base) - cmd := exec.CommandContext(ctx, e.opusencPath, "--bitrate", "128k", wav, out) - if logOut, err := cmd.CombinedOutput(); err != nil { + args := []string{"--bitrate", "128k", wav, out} + logOut, err := e.runCmd(ctx, "opusenc", wav, e.opusencPath, args) + if err != nil { return nil, fmt.Errorf("opusenc: %s %w", logOut, err) } opusFiles = append(opusFiles, out) @@ -299,8 +326,6 @@ func (e *Encoder) encodeVideo(ctx context.Context, input, workDir string, opusFi return fmt.Errorf("calculating zscale: %w", err) } - fmt.Printf("DEBUG zscale: %s (newWidth=%d)\n", zscaleStr, newWidth) - var filters []string if interlaced { filters = append(filters, "bwdif=mode=0:par=-1:-1") @@ -309,7 +334,10 @@ func (e *Encoder) encodeVideo(ctx context.Context, input, workDir string, opusFi filters = append(filters, zscaleStr) } - fmt.Printf("DEBUG final vf: %s\n", strings.Join(filters, ",")) + filterInfo, _ := json.Marshal(map[string]interface{}{ + "zscale": zscaleStr, "new_width": newWidth, "interlaced": interlaced, "vf": strings.Join(filters, ","), + }) + e.log.Debug("video filter chain", input, string(filterInfo)) args := []string{ "-y", @@ -354,7 +382,6 @@ func (e *Encoder) encodeVideo(ctx context.Context, input, workDir string, opusFi continue } args = append(args, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("language=%s", lang)) - fmt.Printf("DEBUG setting audio language: stream %d -> language=%s\n", i, lang) } for _, stream := range streamLangs { @@ -366,7 +393,6 @@ func (e *Encoder) encodeVideo(ctx context.Context, input, workDir string, opusFi } } args = append(args, fmt.Sprintf("-metadata:s:s:%d", idx), fmt.Sprintf("language=%s", stream.Language)) - fmt.Printf("DEBUG setting subtitle language: stream %d -> language=%s\n", idx, stream.Language) } } @@ -387,9 +413,8 @@ func (e *Encoder) encodeVideo(ctx context.Context, input, workDir string, opusFi args = append(args, outFile) args = append([]string{"-hide_banner", "-v", "error"}, args...) - cmd := exec.CommandContext(ctx, e.ffmpegPath, args...) - fmt.Printf("DEBUG FFmpeg command: ffmpeg %s\n", strings.Join(args, " ")) - if out, err := cmd.CombinedOutput(); err != nil { + out, err := e.runCmd(ctx, "ffmpeg encode", input, e.ffmpegPath, args) + if err != nil { return fmt.Errorf("ffmpeg encode: %s %w", out, err) } diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 4789a90..16eb701 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -1,94 +1,116 @@ package logger import ( - "encoding/json" + "database/sql" "fmt" "os" "path/filepath" "time" - "videnc-vibe/pkg/types" + _ "github.com/mattn/go-sqlite3" +) + +const ( + LevelDebug = "debug" + LevelInfo = "info" + LevelError = "error" ) type Logger struct { - infoPath string - errorPath string - structPath string - infoFile *os.File - errorFile *os.File - structFile *os.File + db *sql.DB + dbPath string + retentionDays int } -func New(logDir string) (*Logger, error) { +// New opens (or creates) logs.db inside logDir, applies the schema, and runs +// an initial retention purge. retentionDays <= 0 falls back to 7. +func New(logDir string, retentionDays int) (*Logger, error) { + if retentionDays <= 0 { + retentionDays = 7 + } if err := os.MkdirAll(logDir, 0755); err != nil { return nil, fmt.Errorf("creating log directory: %w", err) } - now := time.Now().Format("2006-01-02") - infoPath := filepath.Join(logDir, fmt.Sprintf("info_%s.log", now)) - errorPath := filepath.Join(logDir, fmt.Sprintf("error_%s.log", now)) - structPath := filepath.Join(logDir, "structured.json") - - infoFile, err := os.OpenFile(infoPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + dbPath := filepath.Join(logDir, "logs.db") + db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&_busy_timeout=5000") if err != nil { - return nil, fmt.Errorf("opening info log: %w", err) + return nil, fmt.Errorf("opening log db: %w", err) } - errorFile, err := os.OpenFile(errorPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return nil, fmt.Errorf("opening error log: %w", err) + schema := ` + CREATE TABLE IF NOT EXISTS logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + level TEXT NOT NULL, + message TEXT NOT NULL, + file TEXT, + extra TEXT + ); + CREATE INDEX IF NOT EXISTS idx_logs_ts_level ON logs(ts, level); + ` + if _, err := db.Exec(schema); err != nil { + db.Close() + return nil, fmt.Errorf("applying log schema: %w", err) } - structFile, err := os.OpenFile(structPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return nil, fmt.Errorf("opening structured log: %w", err) - } + l := &Logger{db: db, dbPath: dbPath, retentionDays: retentionDays} + l.purge() + return l, nil +} - return &Logger{ - infoPath: infoPath, - errorPath: errorPath, - structPath: structPath, - infoFile: infoFile, - errorFile: errorFile, - structFile: structFile, - }, nil +func (l *Logger) purge() { + cutoff := fmt.Sprintf("-%d days", l.retentionDays) + _, _ = l.db.Exec(`DELETE FROM logs WHERE ts < datetime('now', ?)`, cutoff) +} + +func (l *Logger) write(level, message, file, extra string) { + _, _ = l.db.Exec( + `INSERT INTO logs(level, message, file, extra) VALUES(?, ?, ?, ?)`, + level, message, nullIfEmpty(file), nullIfEmpty(extra), + ) +} + +func nullIfEmpty(s string) interface{} { + if s == "" { + return nil + } + return s +} + +func stamp() string { + return time.Now().Format("2006-01-02 15:04:05") } func (l *Logger) Info(message string) { - timestamp := time.Now().Format("2006-01-02 15:04:05") - entry := fmt.Sprintf("[%s] INFO: %s\n", timestamp, message) - l.infoFile.WriteString(entry) - l.writeStructured("info", message, "", "") + fmt.Fprintf(os.Stdout, "[%s] INFO: %s\n", stamp(), message) + l.write(LevelInfo, message, "", "") } -func (l *Logger) Error(message, err string) { - timestamp := time.Now().Format("2006-01-02 15:04:05") - entry := fmt.Sprintf("[%s] ERROR: %s - %s\n", timestamp, message, err) - l.errorFile.WriteString(entry) - l.writeStructured("error", message, err, "") +func (l *Logger) Error(message, errStr string) { + msg := fmt.Sprintf("%s: %s", message, errStr) + fmt.Fprintf(os.Stderr, "[%s] ERROR: %s\n", stamp(), msg) + l.write(LevelError, msg, "", "") } -func (l *Logger) ErrorFile(file string, message, err string) { - timestamp := time.Now().Format("2006-01-02 15:04:05") - entry := fmt.Sprintf("[%s] ERROR: %s - %s (file: %s)\n", timestamp, message, err, file) - l.errorFile.WriteString(entry) - l.writeStructured("error", message, err, file) +func (l *Logger) ErrorFile(file, message, errStr string) { + msg := fmt.Sprintf("%s: %s", message, errStr) + fmt.Fprintf(os.Stderr, "[%s] ERROR: %s (file: %s)\n", stamp(), msg, file) + l.write(LevelError, msg, file, "") } -func (l *Logger) writeStructured(level, message, err, file string) { - entry := types.LogEntry{ - Timestamp: time.Now(), - Level: level, - Message: message, - Error: err, - File: file, - } - data, _ := json.Marshal(entry) - l.structFile.WriteString(string(data) + "\n") +// Debug records a debug entry to the database only. extra is an opaque string, +// typically JSON, used for ffprobe output, API response bodies, or command +// stdout/stderr captures. Pass "" if not applicable. +func (l *Logger) Debug(message, file, extra string) { + l.write(LevelDebug, message, file, extra) } func (l *Logger) Close() { - l.infoFile.Close() - l.errorFile.Close() - l.structFile.Close() + if l.db == nil { + return + } + l.purge() + l.db.Close() + l.db = nil } diff --git a/internal/metadata/metadata.go b/internal/metadata/metadata.go index 7a285c5..6f39e78 100644 --- a/internal/metadata/metadata.go +++ b/internal/metadata/metadata.go @@ -6,11 +6,13 @@ import ( "fmt" "io" "net/http" + "net/url" "regexp" "strconv" "strings" "time" + "videnc-vibe/internal/logger" "videnc-vibe/pkg/types" ) @@ -51,15 +53,49 @@ type TVMazeEpisode struct { type Client struct { omdbAPIKey string httpClient *http.Client + log *logger.Logger } -func NewClient(apiKey string) *Client { +func NewClient(apiKey string, log *logger.Logger) *Client { return &Client{ omdbAPIKey: apiKey, httpClient: &http.Client{Timeout: 10 * time.Second}, + log: log, } } +// redactURL strips secret query parameters (e.g. apikey) before logging. +func redactURL(rawURL string) string { + u, err := url.Parse(rawURL) + if err != nil { + return rawURL + } + q := u.Query() + for _, k := range []string{"apikey", "api_key"} { + if q.Has(k) { + q.Set(k, "REDACTED") + } + } + u.RawQuery = q.Encode() + return u.String() +} + +func (c *Client) logHTTP(label, rawURL string, status int, body []byte) { + if c.log == nil { + return + } + extra, _ := json.Marshal(struct { + URL string `json:"url"` + Status int `json:"status"` + Body string `json:"body"` + }{ + URL: redactURL(rawURL), + Status: status, + Body: string(body), + }) + c.log.Debug(label, "", string(extra)) +} + func ParseFilename(filename string) (isSeries bool, imdbID, tvmazeID, season, episode string) { filename = strings.TrimSuffix(filename, ".mkv") @@ -111,9 +147,9 @@ func ParseMediaType(filename string) types.MediaType { } func (c *Client) FetchMovieMetadata(ctx context.Context, imdbID string) (*types.Metadata, error) { - url := fmt.Sprintf("%s?i=%s&apikey=%s", omdbAPIURL, imdbID, c.omdbAPIKey) + reqURL := fmt.Sprintf("%s?i=%s&apikey=%s", omdbAPIURL, imdbID, c.omdbAPIKey) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil) if err != nil { return nil, fmt.Errorf("building OMDb request: %w", err) } @@ -128,6 +164,8 @@ func (c *Client) FetchMovieMetadata(ctx context.Context, imdbID string) (*types. return nil, fmt.Errorf("reading OMDb body: %w", err) } + c.logHTTP("OMDb GET", reqURL, resp.StatusCode, body) + if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("OMDb HTTP %d: %s", resp.StatusCode, snippet(body)) } @@ -197,8 +235,8 @@ func (c *Client) FetchSeriesMetadata(ctx context.Context, tvmazeID, season, epis }, nil } -func (c *Client) fetchTVMazeJSON(ctx context.Context, url string, v interface{}) error { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) +func (c *Client) fetchTVMazeJSON(ctx context.Context, reqURL string, v interface{}) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil) if err != nil { return fmt.Errorf("build request: %w", err) } @@ -213,6 +251,8 @@ func (c *Client) fetchTVMazeJSON(ctx context.Context, url string, v interface{}) return fmt.Errorf("read body: %w", err) } + c.logHTTP("TVmaze GET", reqURL, resp.StatusCode, body) + if resp.StatusCode != http.StatusOK { return fmt.Errorf("HTTP %d: %s", resp.StatusCode, snippet(body)) } diff --git a/pkg/types/types.go b/pkg/types/types.go index 1103ea3..9180869 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -1,9 +1,5 @@ package types -import ( - "time" -) - type MediaType string const ( @@ -36,9 +32,10 @@ type Metadata struct { } type Config struct { - OMDBAPIKey string `yaml:"omdb_api_key"` - Encoding EncodingConfig - Paths PathsConfig + OMDBAPIKey string `yaml:"omdb_api_key"` + LogRetentionDays int `yaml:"log_retention_days"` + Encoding EncodingConfig + Paths PathsConfig } type EncodingConfig struct { @@ -60,11 +57,3 @@ type PathsConfig struct { Failed string `yaml:"failed"` Work string `yaml:"work"` } - -type LogEntry struct { - Timestamp time.Time `json:"timestamp"` - Level string `json:"level"` - Message string `json:"message"` - File string `json:"file,omitempty"` - Error string `json:"error,omitempty"` -}