mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-20 14:52:22 +02:00
Fix/field hints (#666)
* Added missing field hints * logger intiialization fixed
This commit is contained in:
+4
-10
@@ -22,9 +22,7 @@ use crate::{
|
||||
model::{AppConfig, Config, Healthcheck, HealthcheckConfig, ProcessTargets, SourcesConfig},
|
||||
processing::processor::exec_processing,
|
||||
repository::run_startup_migrations,
|
||||
utils::{
|
||||
apply_config_log_level, config_file_reader, db_viewer, init_logger, request::create_client, resolve_env_var,
|
||||
},
|
||||
utils::{config_file_reader, db_viewer, init_logger, request::create_client, resolve_env_var},
|
||||
};
|
||||
use arc_swap::{access::Access, ArcSwap};
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -145,10 +143,6 @@ const BUILD_TIMESTAMP: &str = env!("VERGEN_BUILD_TIMESTAMP");
|
||||
async fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
// initialize the logger from CLI arg / TULIPROX_LOG env var.
|
||||
// Must happen before everything or we miss log output.
|
||||
init_logger(args.log_level.as_deref());
|
||||
|
||||
db_viewer(&args.db_viewer_args());
|
||||
|
||||
if args.genpwd {
|
||||
@@ -161,8 +155,7 @@ async fn main() {
|
||||
|
||||
let mut config_paths = get_file_paths(&args);
|
||||
|
||||
// Phase 2: apply config-file log level if not already set by CLI / env.
|
||||
apply_config_log_level(args.log_level.as_deref(), config_paths.config_file_path.as_str());
|
||||
init_logger(args.log_level.as_deref(), config_paths.config_file_path.as_str());
|
||||
|
||||
if args.healthcheck {
|
||||
let healthy = healthcheck(config_paths.config_file_path.as_str()).await;
|
||||
@@ -318,7 +311,8 @@ fn get_file_paths(args: &Args) -> ConfigPaths {
|
||||
match utils::read_config_file(&config_file, true, false) {
|
||||
Ok(cfg) => resolve_storage_path(&home_path, cfg.storage_dir.as_deref()),
|
||||
Err(err) => {
|
||||
exit!("Can't read config file {config_file} while resolving storage path: {err}")
|
||||
eprintln!("Can't read config file {config_file} while resolving storage path: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::api::model::{MetadataRetryDbKey, MetadataRetryDbValue};
|
||||
use crate::repository::{BPlusTreeDiskIterator, BPlusTreeQuery, VirtualIdRecord};
|
||||
use log::error;
|
||||
use env_logger::{Builder, Target};
|
||||
use log::{error, LevelFilter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shared::model::{EpgChannel, M3uPlaylistItem, XtreamPlaylistItem};
|
||||
use std::io::Write;
|
||||
@@ -75,6 +76,8 @@ pub fn db_viewer(args: &DbViewerArgs<'_>) {
|
||||
return;
|
||||
}
|
||||
|
||||
init_db_viewer_logger();
|
||||
|
||||
let mut any_processed = false;
|
||||
for request in requests {
|
||||
if let Some(filename) = request.filename {
|
||||
@@ -90,6 +93,12 @@ pub fn db_viewer(args: &DbViewerArgs<'_>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn init_db_viewer_logger() {
|
||||
let mut log_builder = Builder::from_default_env();
|
||||
log_builder.target(Target::Stderr);
|
||||
log_builder.filter_level(LevelFilter::Info);
|
||||
let _ = log_builder.try_init();
|
||||
}
|
||||
|
||||
fn try_dump_typed_db<K, V>(path: &Path) -> bool
|
||||
where
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::model::LogLevelConfig;
|
||||
use crate::utils::config_file_reader;
|
||||
use chrono::{Local, Offset, SecondsFormat};
|
||||
use env_logger::{Builder, Target};
|
||||
use log::{error, info, LevelFilter};
|
||||
use log::{info, LevelFilter};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
@@ -27,104 +27,68 @@ fn get_log_level(log_level: &str) -> LevelFilter {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_log_format(builder: &mut Builder) {
|
||||
builder.format(|buf, record| {
|
||||
pub fn init_logger(user_log_level: Option<&str>, config_file: &str) {
|
||||
|
||||
// tracing_subscriber::registry()
|
||||
// .with(console_subscriber::spawn()) // Console layer
|
||||
// .with(EnvFilter::from_default_env())
|
||||
// .with(fmt::layer()) // stdout logging
|
||||
// .init();
|
||||
|
||||
let env_log_level = std::env::var("TULIPROX_LOG").ok();
|
||||
|
||||
let mut log_builder = Builder::from_default_env();
|
||||
log_builder.target(Target::Stdout);
|
||||
log_builder.format(move |buf, record| {
|
||||
let now = Local::now();
|
||||
let timestamp = now.to_rfc3339_opts(SecondsFormat::Secs, now.offset().fix().local_minus_utc() == 0);
|
||||
writeln!(buf, "[{timestamp} {} {}] {}", record.level(), record.target(), record.args())
|
||||
writeln!(
|
||||
buf,
|
||||
"[{timestamp} {} {}] {}",
|
||||
record.level(),
|
||||
record.target(),
|
||||
record.args()
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
fn apply_level_spec(log_builder: &mut Builder, log_level: &str) -> (LevelFilter, Vec<String>) {
|
||||
// priority CLI-Argument, Env-Var, Config, Default
|
||||
let log_level = user_log_level
|
||||
.map(std::string::ToString::to_string) // cli-argument
|
||||
.or(env_log_level) // env
|
||||
.or_else(|| { // config
|
||||
File::open(config_file).ok()
|
||||
.and_then(|file| serde_saphyr::from_reader::<_, LogLevelConfig>(config_file_reader(file, true))
|
||||
.map_err(|e| eprintln!("Failed to parse log config file: {e}"))
|
||||
.ok())
|
||||
.and_then(|cfg| cfg.log.and_then(|l| l.log_level))
|
||||
})
|
||||
.unwrap_or_else(|| "info".to_string()); // Default
|
||||
|
||||
let mut log_levels = vec![];
|
||||
let effective_max_level;
|
||||
|
||||
if log_level.contains('=') {
|
||||
let mut max_level = LevelFilter::Off;
|
||||
for pair in log_level.split(',') {
|
||||
if pair.contains('=') {
|
||||
let mut kv_iter = pair.split('=').map(str::trim);
|
||||
if let (Some(module), Some(level)) = (kv_iter.next(), kv_iter.next()) {
|
||||
let module_level = get_log_level(level);
|
||||
log_levels.push(format!("{module}={module_level}"));
|
||||
log_builder.filter_module(module, module_level);
|
||||
if module_level > max_level {
|
||||
max_level = module_level;
|
||||
}
|
||||
let log_level = get_log_level(level);
|
||||
log_levels.push(format!("{module}={log_level}"));
|
||||
log_builder.filter_module(module, log_level);
|
||||
}
|
||||
} else {
|
||||
let level = get_log_level(pair);
|
||||
log_levels.push(level.to_string());
|
||||
log_builder.filter_level(level);
|
||||
if level > max_level {
|
||||
max_level = level;
|
||||
}
|
||||
}
|
||||
}
|
||||
effective_max_level = max_level;
|
||||
} else {
|
||||
effective_max_level = get_log_level(log_level);
|
||||
log_builder.filter_level(effective_max_level);
|
||||
log_levels.push(log_level.to_string());
|
||||
// Set the log level based on the parsed value
|
||||
log_builder.filter_level(get_log_level(&log_level));
|
||||
log_levels.push(log_level);
|
||||
}
|
||||
|
||||
(effective_max_level, log_levels)
|
||||
}
|
||||
|
||||
/// Initialize the real logger using CLI argument and `TULIPROX_LOG` env var only.
|
||||
/// Call this BEFORE config paths are resolved so that all `log::error!` / `exit!` calls
|
||||
/// during path resolution are visible.
|
||||
/// Config-file log level is applied later via `apply_config_log_level`.
|
||||
pub fn init_logger(user_log_level: Option<&str>) {
|
||||
let env_log_level = std::env::var("TULIPROX_LOG").ok();
|
||||
let log_level = user_log_level
|
||||
.map(std::string::ToString::to_string)
|
||||
.or(env_log_level)
|
||||
.unwrap_or_else(|| "info".to_string());
|
||||
|
||||
let mut log_builder = Builder::from_default_env();
|
||||
log_builder.target(Target::Stdout);
|
||||
apply_log_format(&mut log_builder);
|
||||
|
||||
let (effective_max_level, log_levels) = apply_level_spec(&mut log_builder, &log_level);
|
||||
|
||||
for module in LOG_ERROR_LEVEL_MOD {
|
||||
log_builder.filter_module(module, LevelFilter::Error);
|
||||
}
|
||||
|
||||
log_builder.init(); // panics if called twice — correct, we only call this once
|
||||
log::set_max_level(effective_max_level);
|
||||
let _ = log_builder.try_init();
|
||||
info!("Log timezone system localtime (TZ)");
|
||||
info!("Log Level {}", &log_levels.join(", "));
|
||||
}
|
||||
|
||||
/// Apply the log level from the config file if no CLI argument or env var
|
||||
/// overrides it. Uses `log::set_max_level` so it works after `init_logger` has
|
||||
/// already registered the `env_logger` instance.
|
||||
/// Call this AFTER config paths are resolved.
|
||||
pub fn apply_config_log_level(user_log_level: Option<&str>, config_file: &str) {
|
||||
let env_log_level = std::env::var("TULIPROX_LOG").ok();
|
||||
|
||||
// Only read from config if neither CLI nor env overrides the level.
|
||||
if user_log_level.is_some() || env_log_level.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(log_level) = File::open(config_file)
|
||||
.ok()
|
||||
.and_then(|file| {
|
||||
serde_saphyr::from_reader::<_, LogLevelConfig>(config_file_reader(file, true))
|
||||
.map_err(|e| error!("Failed to parse log config file: {e}"))
|
||||
.ok()
|
||||
})
|
||||
.and_then(|cfg| cfg.log.and_then(|l| l.log_level))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Build a temporary builder just to parse the level spec (no try_init).
|
||||
let mut dummy = Builder::from_default_env();
|
||||
let (effective_max_level, log_levels) = apply_level_spec(&mut dummy, &log_level);
|
||||
log::set_max_level(effective_max_level);
|
||||
info!("Log Level updated from config: {}", &log_levels.join(", "));
|
||||
}
|
||||
|
||||
@@ -337,6 +337,10 @@
|
||||
"DISCORD": {
|
||||
"TEMPLATE_MESSAGE": "Template message body for Discord notifications."
|
||||
},
|
||||
"DISCORD_MESSAGING_CONFIG": {
|
||||
"TEMPLATES": "Handlebars templates for Discord message bodies.",
|
||||
"URL": "Discord webhook URL for sending notification messages."
|
||||
},
|
||||
"DNS_MAX_ADDRS": "Optional limit of resolved IP addresses kept per hostname. Leave empty for no explicit limit.",
|
||||
"EPG_CONFIG": {
|
||||
"NAME_PREFIX_SEPARATOR": "List of characters (e.g. ':', '|') that separate channel prefixes from the main title during Smart Match.",
|
||||
@@ -366,6 +370,20 @@
|
||||
"PROPRIETARY_DISCOVERY": "Enables proprietary HDHomeRun discovery protocols.",
|
||||
"SSDP_DISCOVERY": "Enables standard SSDP discovery for HDHomeRun emulation on the local network."
|
||||
},
|
||||
"HD_HOME_RUN_DEVICE_CONFIG": {
|
||||
"DEVICE_ID": "Unique device ID used for HDHomeRun identification. Auto-generated if not set.",
|
||||
"DEVICE_TYPE": "Device type string advertised to clients.",
|
||||
"DEVICE_UDN": "Unique device name (UUID) for this virtual HDHomeRun device. Auto-generated if not set.",
|
||||
"FIRMWARE_NAME": "Firmware filename advertised to HDHomeRun clients.",
|
||||
"FIRMWARE_VERSION": "Firmware version string advertised to HDHomeRun clients.",
|
||||
"FRIENDLY_NAME": "Human-readable display name shown to HDHomeRun clients.",
|
||||
"MANUFACTURER": "Manufacturer name advertised to HDHomeRun clients.",
|
||||
"MODEL_NAME": "Model name string advertised to HDHomeRun clients.",
|
||||
"MODEL_NUMBER": "Model number string advertised to HDHomeRun clients.",
|
||||
"NAME": "Internal identifier for this virtual HDHomeRun device. Must be unique.",
|
||||
"PORT": "HTTP port this virtual device listens on. Auto-assigned if 0.",
|
||||
"TUNER_COUNT": "Number of virtual tuners advertised by this device. Minimum 1."
|
||||
},
|
||||
"HD_HOME_RUN_TARGET_OUTPUT": {
|
||||
"DEVICE": "The virtual Device ID exposed by the HDHomeRun emulation.",
|
||||
"USERNAME": "Login username for this account or service."
|
||||
@@ -400,6 +418,7 @@
|
||||
},
|
||||
"LIBRARY_METADATA_READ_CONFIG": {
|
||||
"JELLYFIN": "Reads NFO metadata files formatted for Jellyfin.",
|
||||
"KODI": "Reads NFO metadata files formatted for Kodi.",
|
||||
"PLEX": "Reads XML metadata files or sidecars formatted for Plex."
|
||||
},
|
||||
"LIBRARY_PLAYLIST_CONFIG": {
|
||||
@@ -436,6 +455,7 @@
|
||||
},
|
||||
"MESSAGING_CONFIG": {
|
||||
"NOTIFY_ON": "Select events (e.g. 'info', 'stats', 'error') that trigger notifications for this specific type.",
|
||||
"REST_HEADERS": "Custom HTTP headers sent with REST notification requests (JSON format).",
|
||||
"TELEGRAM_CHAT_IDS": "List of telegram chat IDs to receive notifications. Format: '<chat_id>' or '<chat_id>:<thread_id>'."
|
||||
},
|
||||
"METADATA_LOG_CONFIG": {
|
||||
@@ -445,7 +465,7 @@
|
||||
"SANITIZE_SENSITIVE_INFO": "Attempts to mask passwords and tokens in the logging output for security."
|
||||
},
|
||||
"METADATA_UPDATE_CONFIG": {
|
||||
"METADATA_PATH": "Directory for metadata cache and retry-state files.\n\nRelative paths are resolved against `storage_dir`.\n\nExamples: `metadata`, `/app/data/metadata`.",
|
||||
"CACHE_PATH": "Directory for metadata cache and retry-state files.\n\nRelative paths are resolved against `storage_dir`.\n\nExamples: `metadata`, `/app/data/metadata`.",
|
||||
"MAX_QUEUE_SIZE": "Maximum number of metadata tasks kept in memory for one input worker.\n\nWhen this limit is reached, additional tasks are rejected to protect system stability and memory usage.",
|
||||
"RETRY_DELAY": "Minimum delay for transient requeue scheduling.\n\nUse this to prevent immediate hot-loop retries when tasks are temporarily deferred.\n\nUse duration units like `2s`, `5s`, `10s`.",
|
||||
"WORKER_IDLE_TIMEOUT": "How long a metadata worker waits for new tasks while idle before it can stop itself.\n\nA lower value releases resources sooner; a higher value keeps workers warm for bursty workloads.\n\nUse duration units like `30s`, `1m`, `5m`.",
|
||||
@@ -479,6 +499,11 @@
|
||||
"KEEP_VHOST": "If enabled, keeps the original hostname in the Host header while connecting by resolved IP.",
|
||||
"REFRESH_SECS": "DNS refresh interval in seconds for provider hostnames (minimum effective value is 10)."
|
||||
},
|
||||
"PUSHOVER_MESSAGING_CONFIG": {
|
||||
"TOKEN": "Pushover application API token.",
|
||||
"URL": "Optional custom Pushover API URL. Leave empty to use the default.",
|
||||
"USER": "Pushover user key identifying the notification recipient."
|
||||
},
|
||||
"PROVIDER_FORM": {
|
||||
"URLS": "List of base URLs for this provider. They are used in order and rotated when failover is triggered.",
|
||||
"DNS": {
|
||||
@@ -490,6 +515,7 @@
|
||||
},
|
||||
"PROBE": "Probe controls technical stream analysis retries.\n\nUse this group to define retry steps, retry limits, and cooldown behavior for FFprobe-based stream checks.",
|
||||
"PROBE_CONFIG": {
|
||||
"BACKOFF_JITTER_PERCENT": "Random jitter added to probe retry delays.\n\nA value of 20 means delays vary by ±20%. Helps spread retries across workers to avoid thundering-herd problems.\n\nMust be between 0 and 95.",
|
||||
"COOLDOWN": "Pause time after probe retries are exhausted.\n\nDuring cooldown, that item is skipped for probing. After cooldown ends, probing can start again.\n\nExamples: `1d`, `3d`, `7d`.",
|
||||
"MAX_ATTEMPTS": "How many probe tries are allowed before cooldown starts.\n\nLower values reduce provider load, higher values can recover more transient failures.",
|
||||
"RETRY_BACKOFF_STEP_1": "Wait time after the first probe failure.\n\nUse this as a gentle first retry step.\n\nExample: `10m`.",
|
||||
@@ -535,7 +561,10 @@
|
||||
"BACKOFF_MULTIPLIER": "Multiplies the base backoff after each failed attempt to create an exponential delay."
|
||||
},
|
||||
"REST_MESSAGING_CONFIG": {
|
||||
"HEADERS": "Custom HTTP headers to send with the request (JSON format)."
|
||||
"HEADERS": "Custom HTTP headers to send with the request (JSON format).",
|
||||
"METHOD": "HTTP method used for the notification request (e.g. POST, PUT). Default: POST.",
|
||||
"TEMPLATES": "Handlebars templates for REST notification message bodies.",
|
||||
"URL": "Target URL that receives the notification webhook."
|
||||
},
|
||||
"REVERSE_PROXY_CONFIG": {
|
||||
"FAILOVER_REDIRECT_PATTERNS": "Regex patterns determining which HTTP error responses trigger an automatic failover rotation.",
|
||||
@@ -589,6 +618,14 @@
|
||||
"PROCESSING_ORDER": "Determines the priority or the sequence in which target channels are matched and listed."
|
||||
},
|
||||
"TELEGRAM_CONFIG": {
|
||||
"BOT_TOKEN": "Telegram Bot API token. Obtain this from @BotFather.",
|
||||
"MARKDOWN": "If enabled, messages are sent as MarkdownV2 formatted text.",
|
||||
"TEMPLATES": "Handlebars templates for Telegram message bodies."
|
||||
},
|
||||
"TELEGRAM_MESSAGING_CONFIG": {
|
||||
"BOT_TOKEN": "Telegram Bot API token. Obtain this from @BotFather.",
|
||||
"CHAT_IDS": "List of Telegram chat IDs to receive notifications. Format: '<chat_id>' or '<chat_id>:<thread_id>'.",
|
||||
"MARKDOWN": "If enabled, messages are sent as MarkdownV2 formatted text.",
|
||||
"TEMPLATES": "Handlebars templates for Telegram message bodies."
|
||||
},
|
||||
"TMDB_CONFIG": {
|
||||
|
||||
Reference in New Issue
Block a user