Merge branch 'master' into feat-logging-to-file
This commit is contained in:
+24
-8
@@ -1,6 +1,10 @@
|
||||
- By default go2rtc will search config file `go2rtc.yaml` in current work directory
|
||||
- go2rtc support multiple config files
|
||||
- go2rtc support inline config as `YAML`, `JSON` or `key=value` format from command line
|
||||
- go2rtc support multiple config files:
|
||||
- `go2rtc -c config1.yaml -c config2.yaml -c config3.yaml`
|
||||
- go2rtc support inline config as multiple formats from command line:
|
||||
- **YAML**: `go2rtc -c '{log: {format: text}}'`
|
||||
- **JSON**: `go2rtc -c '{"log":{"format":"text"}}'`
|
||||
- **key=value**: `go2rtc -c log.format=text`
|
||||
- Every next config will overwrite previous (but only defined params)
|
||||
|
||||
```
|
||||
@@ -15,21 +19,30 @@ go2rtc -c log.format=text -c /config/go2rtc.yaml -c rtsp.listen='' -c /usr/local
|
||||
|
||||
## Environment variables
|
||||
|
||||
Also go2rtc support templates for using environment variables in any part of config:
|
||||
There is support for loading external variables into the config. First, they will be attempted to be loaded from [credential files](https://systemd.io/CREDENTIALS). If `CREDENTIALS_DIRECTORY` is not set, then the key will be loaded from an environment variable. If no environment variable is set, then the string will be left as-is.
|
||||
|
||||
```yaml
|
||||
streams:
|
||||
camera1: rtsp://rtsp:${CAMERA_PASSWORD}@192.168.1.123/av_stream/ch0
|
||||
|
||||
${LOGS:} # empty default value
|
||||
|
||||
rtsp:
|
||||
username: ${RTSP_USER:admin} # "admin" if env "RTSP_USER" not set
|
||||
password: ${RTSP_PASS:secret} # "secret" if env "RTSP_PASS" not set
|
||||
username: ${RTSP_USER:admin} # "admin" if "RTSP_USER" not set
|
||||
password: ${RTSP_PASS:secret} # "secret" if "RTSP_PASS" not set
|
||||
```
|
||||
|
||||
## JSON Schema
|
||||
|
||||
Editors like [GoLand](https://www.jetbrains.com/go/) and [VS Code](https://code.visualstudio.com/) supports autocomplete and syntax validation.
|
||||
|
||||
```yaml
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/AlexxIT/go2rtc/master/website/schema.json
|
||||
```
|
||||
|
||||
## Defaults
|
||||
|
||||
- Default values may change in updates
|
||||
- FFmpeg module has many presets, they are not listed here because they may also change in updates
|
||||
|
||||
```yaml
|
||||
api:
|
||||
listen: ":1984"
|
||||
@@ -38,7 +51,10 @@ ffmpeg:
|
||||
bin: "ffmpeg"
|
||||
|
||||
log:
|
||||
format: "color"
|
||||
level: "info"
|
||||
output: "stdout"
|
||||
time: "UNIXMS"
|
||||
|
||||
rtsp:
|
||||
listen: ":8554"
|
||||
@@ -51,4 +67,4 @@ webrtc:
|
||||
listen: ":8555/tcp"
|
||||
ice_servers:
|
||||
- urls: [ "stun:stun.l.google.com:19302" ]
|
||||
```
|
||||
```
|
||||
|
||||
+21
-146
@@ -1,29 +1,20 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/shell"
|
||||
"github.com/AlexxIT/go2rtc/pkg/yaml"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var Version = "1.9.2"
|
||||
var UserAgent = "go2rtc/" + Version
|
||||
|
||||
var ConfigPath string
|
||||
var Info = map[string]any{
|
||||
"version": Version,
|
||||
}
|
||||
var (
|
||||
Version string
|
||||
UserAgent string
|
||||
ConfigPath string
|
||||
Info = make(map[string]any)
|
||||
)
|
||||
|
||||
const usage = `Usage of go2rtc:
|
||||
|
||||
@@ -33,12 +24,12 @@ const usage = `Usage of go2rtc:
|
||||
`
|
||||
|
||||
func Init() {
|
||||
var confs Config
|
||||
var config flagConfig
|
||||
var daemon bool
|
||||
var version bool
|
||||
|
||||
flag.Var(&confs, "config", "")
|
||||
flag.Var(&confs, "c", "")
|
||||
flag.Var(&config, "config", "")
|
||||
flag.Var(&config, "c", "")
|
||||
flag.BoolVar(&daemon, "daemon", false, "")
|
||||
flag.BoolVar(&daemon, "d", false, "")
|
||||
flag.BoolVar(&version, "version", false, "")
|
||||
@@ -54,133 +45,39 @@ func Init() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if daemon {
|
||||
if daemon && os.Getppid() != 1 {
|
||||
if runtime.GOOS == "windows" {
|
||||
fmt.Println("Daemon not supported on Windows")
|
||||
fmt.Println("Daemon mode is not supported on Windows")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
args := os.Args[1:]
|
||||
for i, arg := range args {
|
||||
if arg == "-daemon" {
|
||||
args[i] = ""
|
||||
}
|
||||
}
|
||||
// Re-run the program in background and exit
|
||||
cmd := exec.Command(os.Args[0], args...)
|
||||
cmd := exec.Command(os.Args[0], os.Args[1:]...)
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Fatal().Err(err).Send()
|
||||
fmt.Println("Failed to start daemon:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("Running in daemon mode with PID:", cmd.Process.Pid)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if confs == nil {
|
||||
confs = []string{"go2rtc.yaml"}
|
||||
}
|
||||
|
||||
for _, conf := range confs {
|
||||
if len(conf) == 0 {
|
||||
continue
|
||||
}
|
||||
if conf[0] == '{' {
|
||||
// config as raw YAML or JSON
|
||||
configs = append(configs, []byte(conf))
|
||||
} else if data := parseConfString(conf); data != nil {
|
||||
configs = append(configs, data)
|
||||
} else {
|
||||
// config as file
|
||||
if ConfigPath == "" {
|
||||
ConfigPath = conf
|
||||
}
|
||||
|
||||
if data, _ = os.ReadFile(conf); data == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
data = []byte(shell.ReplaceEnvVars(string(data)))
|
||||
configs = append(configs, data)
|
||||
}
|
||||
}
|
||||
|
||||
if ConfigPath != "" {
|
||||
if !filepath.IsAbs(ConfigPath) {
|
||||
if cwd, err := os.Getwd(); err == nil {
|
||||
ConfigPath = filepath.Join(cwd, ConfigPath)
|
||||
}
|
||||
}
|
||||
Info["config_path"] = ConfigPath
|
||||
}
|
||||
UserAgent = "go2rtc/" + Version
|
||||
|
||||
Info["version"] = Version
|
||||
Info["revision"] = revision
|
||||
|
||||
var cfg struct {
|
||||
Mod map[string]string `yaml:"log"`
|
||||
}
|
||||
|
||||
cfg.Mod = map[string]string{
|
||||
"format": "", // useless, but anyway
|
||||
"level": "info",
|
||||
"output": "stdout", // TODO: change to stderr someday
|
||||
"time": zerolog.TimeFormatUnixMs,
|
||||
}
|
||||
|
||||
LoadConfig(&cfg)
|
||||
|
||||
log.Logger = NewLogger(cfg.Mod)
|
||||
|
||||
modules = cfg.Mod
|
||||
initConfig(config)
|
||||
initLogger()
|
||||
|
||||
platform := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
log.Info().Str("version", Version).Str("platform", platform).Str("revision", revision).Msg("go2rtc")
|
||||
log.Debug().Str("version", runtime.Version()).Str("vcs.time", vcsTime).Msg("build")
|
||||
Logger.Info().Str("version", Version).Str("platform", platform).Str("revision", revision).Msg("go2rtc")
|
||||
Logger.Debug().Str("version", runtime.Version()).Str("vcs.time", vcsTime).Msg("build")
|
||||
|
||||
if ConfigPath != "" {
|
||||
log.Info().Str("path", ConfigPath).Msg("config")
|
||||
}
|
||||
|
||||
migrateStore()
|
||||
}
|
||||
|
||||
func LoadConfig(v any) {
|
||||
for _, data := range configs {
|
||||
if err := yaml.Unmarshal(data, v); err != nil {
|
||||
log.Warn().Err(err).Msg("[app] read config")
|
||||
}
|
||||
Logger.Info().Str("path", ConfigPath).Msg("config")
|
||||
}
|
||||
}
|
||||
|
||||
func PatchConfig(key string, value any, path ...string) error {
|
||||
if ConfigPath == "" {
|
||||
return errors.New("config file disabled")
|
||||
}
|
||||
|
||||
// empty config is OK
|
||||
b, _ := os.ReadFile(ConfigPath)
|
||||
|
||||
b, err := yaml.Patch(b, key, value, path...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(ConfigPath, b, 0644)
|
||||
}
|
||||
|
||||
// internal
|
||||
|
||||
type Config []string
|
||||
|
||||
func (c *Config) String() string {
|
||||
return strings.Join(*c, " ")
|
||||
}
|
||||
|
||||
func (c *Config) Set(value string) error {
|
||||
*c = append(*c, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
var configs [][]byte
|
||||
|
||||
func readRevisionTime() (revision, vcsTime string) {
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
for _, setting := range info.Settings {
|
||||
@@ -202,25 +99,3 @@ func readRevisionTime() (revision, vcsTime string) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseConfString(s string) []byte {
|
||||
i := strings.IndexByte(s, '=')
|
||||
if i < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
items := strings.Split(s[:i], ".")
|
||||
if len(items) < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// `log.level=trace` => `{log: {level: trace}}`
|
||||
var pre string
|
||||
var suf = s[i+1:]
|
||||
for _, item := range items {
|
||||
pre += "{" + item + ": "
|
||||
suf += "}"
|
||||
}
|
||||
|
||||
return []byte(pre + suf)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/shell"
|
||||
"github.com/AlexxIT/go2rtc/pkg/yaml"
|
||||
)
|
||||
|
||||
func LoadConfig(v any) {
|
||||
for _, data := range configs {
|
||||
if err := yaml.Unmarshal(data, v); err != nil {
|
||||
Logger.Warn().Err(err).Send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PatchConfig(key string, value any, path ...string) error {
|
||||
if ConfigPath == "" {
|
||||
return errors.New("config file disabled")
|
||||
}
|
||||
|
||||
// empty config is OK
|
||||
b, _ := os.ReadFile(ConfigPath)
|
||||
|
||||
b, err := yaml.Patch(b, key, value, path...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(ConfigPath, b, 0644)
|
||||
}
|
||||
|
||||
type flagConfig []string
|
||||
|
||||
func (c *flagConfig) String() string {
|
||||
return strings.Join(*c, " ")
|
||||
}
|
||||
|
||||
func (c *flagConfig) Set(value string) error {
|
||||
*c = append(*c, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
var configs [][]byte
|
||||
|
||||
func initConfig(confs flagConfig) {
|
||||
if confs == nil {
|
||||
confs = []string{"go2rtc.yaml"}
|
||||
}
|
||||
|
||||
for _, conf := range confs {
|
||||
if len(conf) == 0 {
|
||||
continue
|
||||
}
|
||||
if conf[0] == '{' {
|
||||
// config as raw YAML or JSON
|
||||
configs = append(configs, []byte(conf))
|
||||
} else if data := parseConfString(conf); data != nil {
|
||||
configs = append(configs, data)
|
||||
} else {
|
||||
// config as file
|
||||
if ConfigPath == "" {
|
||||
ConfigPath = conf
|
||||
}
|
||||
|
||||
if data, _ = os.ReadFile(conf); data == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
data = []byte(shell.ReplaceEnvVars(string(data)))
|
||||
configs = append(configs, data)
|
||||
}
|
||||
}
|
||||
|
||||
if ConfigPath != "" {
|
||||
if !filepath.IsAbs(ConfigPath) {
|
||||
if cwd, err := os.Getwd(); err == nil {
|
||||
ConfigPath = filepath.Join(cwd, ConfigPath)
|
||||
}
|
||||
}
|
||||
Info["config_path"] = ConfigPath
|
||||
}
|
||||
}
|
||||
|
||||
func parseConfString(s string) []byte {
|
||||
i := strings.IndexByte(s, '=')
|
||||
if i < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
items := strings.Split(s[:i], ".")
|
||||
if len(items) < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// `log.level=trace` => `{log: {level: trace}}`
|
||||
var pre string
|
||||
var suf = s[i+1:]
|
||||
for _, item := range items {
|
||||
pre += "{" + item + ": "
|
||||
suf += "}"
|
||||
}
|
||||
|
||||
return []byte(pre + suf)
|
||||
}
|
||||
+35
-23
@@ -7,20 +7,39 @@ import (
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var MemoryLog = newBuffer(16)
|
||||
|
||||
// NewLogger support:
|
||||
func GetLogger(module string) zerolog.Logger {
|
||||
if s, ok := modules[module]; ok {
|
||||
lvl, err := zerolog.ParseLevel(s)
|
||||
if err == nil {
|
||||
return Logger.Level(lvl)
|
||||
}
|
||||
Logger.Warn().Err(err).Caller().Send()
|
||||
}
|
||||
|
||||
return Logger
|
||||
}
|
||||
|
||||
// initLogger support:
|
||||
// - output: empty (only to memory), stderr, stdout
|
||||
// - format: empty (autodetect color support), color, json, text
|
||||
// - time: empty (disable timestamp), UNIXMS, UNIXMICRO, UNIXNANO
|
||||
// - level: disabled, trace, debug, info, warn, error...
|
||||
func NewLogger(config map[string]string) zerolog.Logger {
|
||||
func initLogger() {
|
||||
var cfg struct {
|
||||
Mod map[string]string `yaml:"log"`
|
||||
}
|
||||
|
||||
cfg.Mod = modules // defaults
|
||||
|
||||
LoadConfig(&cfg)
|
||||
|
||||
var writer io.Writer
|
||||
|
||||
switch output, path, _ := strings.Cut(config["output"], ":"); output {
|
||||
switch output, path, _ := strings.Cut(modules["output"], ":"); output {
|
||||
case "stderr":
|
||||
writer = os.Stderr
|
||||
case "stdout":
|
||||
@@ -33,10 +52,10 @@ func NewLogger(config map[string]string) zerolog.Logger {
|
||||
writer, _ = os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
}
|
||||
|
||||
timeFormat := config["time"]
|
||||
timeFormat := modules["time"]
|
||||
|
||||
if writer != nil {
|
||||
if format := config["format"]; format != "json" {
|
||||
if format := modules["format"]; format != "json" {
|
||||
console := &zerolog.ConsoleWriter{Out: writer}
|
||||
|
||||
switch format {
|
||||
@@ -68,31 +87,24 @@ func NewLogger(config map[string]string) zerolog.Logger {
|
||||
writer = MemoryLog
|
||||
}
|
||||
|
||||
logger := zerolog.New(writer)
|
||||
lvl, _ := zerolog.ParseLevel(modules["level"])
|
||||
Logger = zerolog.New(writer).Level(lvl)
|
||||
|
||||
if timeFormat != "" {
|
||||
zerolog.TimeFieldFormat = timeFormat
|
||||
logger = logger.With().Timestamp().Logger()
|
||||
Logger = Logger.With().Timestamp().Logger()
|
||||
}
|
||||
|
||||
lvl, _ := zerolog.ParseLevel(config["level"])
|
||||
return logger.Level(lvl)
|
||||
}
|
||||
|
||||
func GetLogger(module string) zerolog.Logger {
|
||||
if s, ok := modules[module]; ok {
|
||||
lvl, err := zerolog.ParseLevel(s)
|
||||
if err == nil {
|
||||
return log.Level(lvl)
|
||||
}
|
||||
log.Warn().Err(err).Caller().Send()
|
||||
}
|
||||
|
||||
return log.Logger
|
||||
}
|
||||
var Logger zerolog.Logger
|
||||
|
||||
// modules log levels
|
||||
var modules map[string]string
|
||||
var modules = map[string]string{
|
||||
"format": "", // useless, but anyway
|
||||
"level": "info",
|
||||
"output": "stdout", // TODO: change to stderr someday
|
||||
"time": zerolog.TimeFormatUnixMs,
|
||||
}
|
||||
|
||||
const chunkSize = 1 << 16
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func migrateStore() {
|
||||
const name = "go2rtc.json"
|
||||
|
||||
data, _ := os.ReadFile(name)
|
||||
if data == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var store struct {
|
||||
Streams map[string]string `json:"streams"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &store); err != nil {
|
||||
log.Warn().Err(err).Caller().Send()
|
||||
return
|
||||
}
|
||||
|
||||
for id, url := range store.Streams {
|
||||
if err := PatchConfig(id, url, "streams"); err != nil {
|
||||
log.Warn().Err(err).Caller().Send()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ = os.Remove(name)
|
||||
}
|
||||
Reference in New Issue
Block a user