fix(v2): log redacted ffmpeg/ffprobe stderr on stream failure

The stream worker discarded ffmpeg stderr, so reconnect loops were opaque.
Capture stderr and log it with the RTSP URL/credentials redacted, so the
real cause (auth, timeout, option, URL parse) is diagnosable without
leaking the source address.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ila
2026-07-23 20:31:00 +08:00
co-authored by Claude Opus 4.8
parent f58728cddd
commit 99226c7479
+22 -2
View File
@@ -2,18 +2,32 @@
package source package source
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"os/exec" "os/exec"
"regexp"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
) )
var rtspURLPattern = regexp.MustCompile(`rtsp://[^\s'"]+`)
// redact removes the resolved source URL (which may carry credentials) from any
// FFmpeg/FFprobe diagnostic text before it is logged.
func redact(text, sourceURL string) string {
if sourceURL != "" {
text = strings.ReplaceAll(text, sourceURL, "<source>")
}
return rtspURLPattern.ReplaceAllString(text, "rtsp://<redacted>")
}
type Status string type Status string
const ( const (
@@ -195,8 +209,12 @@ func probe(ctx context.Context, config Config) (Metadata, error) {
args := []string{"-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height,r_frame_rate", "-of", "json"} args := []string{"-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height,r_frame_rate", "-of", "json"}
args = appendInputOptions(args, config) args = appendInputOptions(args, config)
args = append(args, "-i", config.SourceURL) args = append(args, "-i", config.SourceURL)
output, err := exec.CommandContext(ctx, config.FFprobePath, args...).Output() command := exec.CommandContext(ctx, config.FFprobePath, args...)
var stderr bytes.Buffer
command.Stderr = &stderr
output, err := command.Output()
if err != nil { if err != nil {
log.Printf("ffprobe 探测失败:%s(%v)", strings.TrimSpace(redact(stderr.String(), config.SourceURL)), err)
return Metadata{}, errors.New("unable to probe video metadata") return Metadata{}, errors.New("unable to probe video metadata")
} }
var parsed struct { var parsed struct {
@@ -230,7 +248,8 @@ func decode(ctx context.Context, config Config, metadata Metadata, publish func(
args := append([]string{"-hide_banner", "-loglevel", "error"}, appendInputOptions(nil, config)...) args := append([]string{"-hide_banner", "-loglevel", "error"}, appendInputOptions(nil, config)...)
args = append(args, "-i", config.SourceURL, "-an", "-sn", "-dn", "-f", "rawvideo", "-pix_fmt", "bgr24", "-") args = append(args, "-i", config.SourceURL, "-an", "-sn", "-dn", "-f", "rawvideo", "-pix_fmt", "bgr24", "-")
command := exec.CommandContext(ctx, config.FFmpegPath, args...) command := exec.CommandContext(ctx, config.FFmpegPath, args...)
command.Stderr = io.Discard var stderr bytes.Buffer
command.Stderr = &stderr
stdout, err := command.StdoutPipe() stdout, err := command.StdoutPipe()
if err != nil { if err != nil {
return errors.New("unable to read video frames") return errors.New("unable to read video frames")
@@ -247,6 +266,7 @@ func decode(ctx context.Context, config Config, metadata Metadata, publish func(
if ctx.Err() != nil { if ctx.Err() != nil {
return ctx.Err() return ctx.Err()
} }
log.Printf("ffmpeg 解码停止:%s(%v)", strings.TrimSpace(redact(stderr.String(), config.SourceURL)), err)
return errors.New("video decoder stopped") return errors.New("video decoder stopped")
} }
publish(frame) publish(frame)