channel_log: shared disk logging with round-trip
Move IRC's disk logging to thalamus/channel_log.rs as shared functions: append_disk_log() and load_disk_log(). Any daemon can use them — opt-in, not mandatory (tmux won't use them). load_disk_log() populates a ChannelLog from disk on startup, so history survives daemon restarts. IRC daemon now uses the shared functions. Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
parent
a6ffe9e086
commit
58737a2cef
5 changed files with 72 additions and 17 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
|
@ -2593,6 +2593,7 @@ dependencies = [
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
|
|
@ -2902,6 +2903,18 @@ dependencies = [
|
||||||
"zmij",
|
"zmij",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_urlencoded"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.9"
|
version = "0.10.9"
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ use tokio_util::compat::TokioAsyncReadCompatExt;
|
||||||
use log::{info, warn, error};
|
use log::{info, warn, error};
|
||||||
|
|
||||||
use poc_memory::channel_capnp::{channel_client, channel_server};
|
use poc_memory::channel_capnp::{channel_client, channel_server};
|
||||||
|
use poc_memory::thalamus::channel_log;
|
||||||
|
|
||||||
// ── Constants ──────────────────────────────────────────────────
|
// ── Constants ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -229,23 +230,12 @@ impl State {
|
||||||
|
|
||||||
// ── Persistence ────────────────────────────────────────────────
|
// ── Persistence ────────────────────────────────────────────────
|
||||||
|
|
||||||
fn data_dir() -> PathBuf {
|
fn log_dir() -> PathBuf {
|
||||||
dirs::home_dir().unwrap_or_default().join(".consciousness/channels/irc.logs")
|
channel_log::log_dir("irc")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn append_log(target: &str, nick: &str, text: &str) {
|
fn append_log(target: &str, nick: &str, text: &str) {
|
||||||
use std::io::Write;
|
channel_log::append_disk_log(&log_dir(), target, nick, text);
|
||||||
let filename = format!("{}.log", target.trim_start_matches('#').to_lowercase());
|
|
||||||
let dir = data_dir().join("logs");
|
|
||||||
let _ = std::fs::create_dir_all(&dir);
|
|
||||||
if let Ok(mut f) = std::fs::OpenOptions::new()
|
|
||||||
.create(true)
|
|
||||||
.append(true)
|
|
||||||
.open(dir.join(&filename))
|
|
||||||
{
|
|
||||||
let secs = now() as u64;
|
|
||||||
let _ = writeln!(f, "{secs} <{nick}> {text}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn now() -> f64 {
|
fn now() -> f64 {
|
||||||
|
|
@ -266,7 +256,7 @@ fn root_certs() -> rustls::RootCertStore {
|
||||||
// ── IRC Connection Loop ────────────────────────────────────────
|
// ── IRC Connection Loop ────────────────────────────────────────
|
||||||
|
|
||||||
async fn connection_loop(state: SharedState) {
|
async fn connection_loop(state: SharedState) {
|
||||||
let _ = std::fs::create_dir_all(data_dir().join("logs"));
|
let _ = std::fs::create_dir_all(log_dir());
|
||||||
let mut backoff = RECONNECT_BASE_SECS;
|
let mut backoff = RECONNECT_BASE_SECS;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ capnp-rpc = "0.20"
|
||||||
dirs = "6"
|
dirs = "6"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
poc-memory = { path = "../.." }
|
poc-memory = { path = "../.." }
|
||||||
reqwest = { version = "0.13", default-features = false, features = ["json", "rustls"] }
|
reqwest = { version = "0.13", default-features = false, features = ["json", "form", "rustls"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ use tokio::net::UnixListener;
|
||||||
use tokio_util::compat::TokioAsyncReadCompatExt;
|
use tokio_util::compat::TokioAsyncReadCompatExt;
|
||||||
use log::{info, warn, error};
|
use log::{info, warn, error};
|
||||||
|
|
||||||
use poc_memory::channel_capnp::{channel_client, channel_server};
|
use poc_memory::channel_capnp::channel_server;
|
||||||
use poc_memory::thalamus::channel_log::ChannelLog;
|
use poc_memory::thalamus::channel_log::ChannelLog;
|
||||||
|
|
||||||
// ── Config ─────────────────────────────────────────────────────
|
// ── Config ─────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -84,3 +84,55 @@ impl ChannelLog {
|
||||||
.join("\n")
|
.join("\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Disk logging ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Append a timestamped message to a per-channel log file.
|
||||||
|
///
|
||||||
|
/// Log path: `{log_dir}/{channel}.log`
|
||||||
|
/// Format: `{unix_secs} <{nick}> {text}`
|
||||||
|
pub fn append_disk_log(log_dir: &std::path::Path, channel: &str, nick: &str, text: &str) {
|
||||||
|
use std::io::Write;
|
||||||
|
let filename = format!("{}.log", channel.trim_start_matches('#').to_lowercase());
|
||||||
|
let _ = std::fs::create_dir_all(log_dir);
|
||||||
|
if let Ok(mut f) = std::fs::OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(log_dir.join(&filename))
|
||||||
|
{
|
||||||
|
let secs = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_secs();
|
||||||
|
let _ = writeln!(f, "{secs} <{nick}> {text}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a ChannelLog from a disk log file, populating with the last N lines.
|
||||||
|
pub fn load_disk_log(log_dir: &std::path::Path, channel: &str) -> ChannelLog {
|
||||||
|
use std::io::{BufRead, BufReader};
|
||||||
|
let filename = format!("{}.log", channel.trim_start_matches('#').to_lowercase());
|
||||||
|
let path = log_dir.join(&filename);
|
||||||
|
|
||||||
|
let mut log = ChannelLog::new();
|
||||||
|
let Ok(file) = std::fs::File::open(&path) else { return log };
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
|
// Read all lines, keep only the last DEFAULT_CAPACITY
|
||||||
|
let lines: Vec<String> = reader.lines().flatten().collect();
|
||||||
|
for line in lines.into_iter().rev().take(DEFAULT_CAPACITY).collect::<Vec<_>>().into_iter().rev() {
|
||||||
|
log.push(line);
|
||||||
|
}
|
||||||
|
// Mark all loaded lines as consumed (they're history, not new)
|
||||||
|
log.consumed = log.total;
|
||||||
|
log
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Standard log directory for a channel daemon.
|
||||||
|
pub fn log_dir(daemon_name: &str) -> PathBuf {
|
||||||
|
dirs::home_dir()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.join(format!(".consciousness/channels/{}.logs", daemon_name))
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue