[refactor](decoders): extract decoder logging into trx-decode-log crate

Move DecoderLoggers and DecodeLogsConfig out of trx-server into a
dedicated src/decoders/trx-decode-log crate, giving file logging the
same standalone crate treatment as the four decoder crates.

- src/decoders/trx-decode-log/ (new — DecodeLogsConfig + DecoderLoggers)
- trx-server/config.rs: re-exports DecodeLogsConfig from trx-decode-log
  so ServerConfig field references and all tests compile unchanged
- trx-server: drop decode_logs module, use trx_decode_log directly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 18:36:04 +01:00
parent f4b92a0f20
commit b9005acffd
8 changed files with 98 additions and 48 deletions
+16
View File
@@ -0,0 +1,16 @@
# SPDX-FileCopyrightText: 2026 Stanislaw Grams <stanislawgrams@gmail.com>
#
# SPDX-License-Identifier: BSD-2-Clause
[package]
name = "trx-decode-log"
version = "0.1.0"
edition = "2021"
[dependencies]
trx-core = { path = "../../trx-core" }
chrono = { version = "0.4", default-features = false, features = ["clock"] }
dirs = "6"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tracing = { workspace = true }
@@ -1,7 +1,12 @@
// SPDX-FileCopyrightText: 2025 Stanislaw Grams <stanislawgrams@gmail.com>
// SPDX-FileCopyrightText: 2026 Stanislaw Grams <stanislawgrams@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
//! Server-side decoder file logging (APRS / CW / FT8 / WSPR).
//!
//! Provides [`DecodeLogsConfig`] for TOML configuration and [`DecoderLoggers`]
//! for writing JSON-Lines log files with automatic daily rotation.
use std::fs::{create_dir_all, File, OpenOptions};
use std::io::{BufWriter, Write};
use std::path::{Path, PathBuf};
@@ -9,12 +14,62 @@ use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
use chrono::Utc;
use serde::{Deserialize, Serialize};
use serde_json::json;
use tracing::warn;
use crate::config::DecodeLogsConfig;
use trx_core::decode::{AprsPacket, CwEvent, Ft8Message, WsprMessage};
// ---------------------------------------------------------------------------
// Configuration
// ---------------------------------------------------------------------------
fn default_decode_logs_dir() -> String {
if let Some(data_dir) = dirs::data_dir() {
return data_dir
.join("trx-rs")
.join("decoders")
.to_string_lossy()
.to_string();
}
"logs/decoders".to_string()
}
/// Server-side decoder file logging configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct DecodeLogsConfig {
/// Whether decoder file logging is enabled
pub enabled: bool,
/// Base directory for log files
pub dir: String,
/// APRS decoder log filename
pub aprs_file: String,
/// CW decoder log filename
pub cw_file: String,
/// FT8 decoder log filename
pub ft8_file: String,
/// WSPR decoder log filename
pub wspr_file: String,
}
impl Default for DecodeLogsConfig {
fn default() -> Self {
Self {
enabled: false,
dir: default_decode_logs_dir(),
aprs_file: "TRXRS-APRS-%YYYY%-%MM%-%DD%.log".to_string(),
cw_file: "TRXRS-CW-%YYYY%-%MM%-%DD%.log".to_string(),
ft8_file: "TRXRS-FT8-%YYYY%-%MM%-%DD%.log".to_string(),
wspr_file: "TRXRS-WSPR-%YYYY%-%MM%-%DD%.log".to_string(),
}
}
}
// ---------------------------------------------------------------------------
// File logger (private)
// ---------------------------------------------------------------------------
struct DecoderFileLogger {
base_dir: PathBuf,
file_template: String,
@@ -64,7 +119,7 @@ impl DecoderFileLogger {
})
}
fn write_payload<T: serde::Serialize>(&self, payload: &T) {
fn write_payload<T: Serialize>(&self, payload: &T) {
let ts_ms = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(d) => d.as_millis() as u64,
Err(_) => 0,
@@ -106,6 +161,11 @@ impl DecoderFileLogger {
}
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/// Aggregate logger for all four server-side decoders.
pub struct DecoderLoggers {
aprs: DecoderFileLogger,
cw: DecoderFileLogger,
@@ -114,6 +174,7 @@ pub struct DecoderLoggers {
}
impl DecoderLoggers {
/// Create loggers from config, or return `None` when logging is disabled.
pub fn from_config(cfg: &DecodeLogsConfig) -> Result<Option<Arc<Self>>, String> {
if !cfg.enabled {
return Ok(None);
+1
View File
@@ -26,6 +26,7 @@ trx-backend = { path = "trx-backend" }
trx-core = { path = "../trx-core" }
trx-aprs = { path = "../decoders/trx-aprs" }
trx-cw = { path = "../decoders/trx-cw" }
trx-decode-log = { path = "../decoders/trx-decode-log" }
trx-ft8 = { path = "../decoders/trx-ft8" }
trx-wspr = { path = "../decoders/trx-wspr" }
trx-protocol = { path = "../trx-protocol" }
+1 -1
View File
@@ -28,7 +28,7 @@ use trx_ft8::Ft8Decoder;
use trx_wspr::WsprDecoder;
use crate::config::AudioConfig;
use crate::decode_logs::DecoderLoggers;
use trx_decode_log::DecoderLoggers;
const APRS_HISTORY_RETENTION: Duration = Duration::from_secs(24 * 60 * 60);
const FT8_HISTORY_RETENTION: Duration = Duration::from_secs(24 * 60 * 60);
+1 -42
View File
@@ -16,6 +16,7 @@ use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use trx_app::{ConfigError, ConfigFile};
pub use trx_decode_log::DecodeLogsConfig;
use trx_core::rig::state::RigMode;
@@ -260,48 +261,6 @@ impl Default for AprsFiConfig {
}
}
fn default_decode_logs_dir() -> String {
if let Some(data_dir) = dirs::data_dir() {
return data_dir
.join("trx-rs")
.join("decoders")
.to_string_lossy()
.to_string();
}
"logs/decoders".to_string()
}
/// Server-side decoder file logging configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct DecodeLogsConfig {
/// Whether decoder file logging is enabled
pub enabled: bool,
/// Base directory for log files
pub dir: String,
/// APRS decoder log filename
pub aprs_file: String,
/// CW decoder log filename
pub cw_file: String,
/// FT8 decoder log filename
pub ft8_file: String,
/// WSPR decoder log filename
pub wspr_file: String,
}
impl Default for DecodeLogsConfig {
fn default() -> Self {
Self {
enabled: false,
dir: default_decode_logs_dir(),
aprs_file: "TRXRS-APRS-%YYYY%-%MM%-%DD%.log".to_string(),
cw_file: "TRXRS-CW-%YYYY%-%MM%-%DD%.log".to_string(),
ft8_file: "TRXRS-FT8-%YYYY%-%MM%-%DD%.log".to_string(),
wspr_file: "TRXRS-WSPR-%YYYY%-%MM%-%DD%.log".to_string(),
}
}
}
impl ServerConfig {
pub fn validate(&self) -> Result<(), String> {
validate_log_level(self.general.log_level.as_deref())?;
+1 -2
View File
@@ -5,7 +5,6 @@
mod aprsfi;
mod audio;
mod config;
mod decode_logs;
mod error;
mod listener;
mod pskreporter;
@@ -34,7 +33,7 @@ use trx_core::rig::state::RigState;
use trx_core::DynResult;
use config::ServerConfig;
use decode_logs::DecoderLoggers;
use trx_decode_log::DecoderLoggers;
const PKG_DESCRIPTION: &str = concat!(env!("CARGO_PKG_NAME"), " - rig server daemon");
const RIG_TASK_CHANNEL_BUFFER: usize = 32;