Added comprehensive timestamp formatting to both agent logs and UI:
Rust Agent Logger:
- Added chrono dependency to Cargo.toml
- Updated SimpleLogger to include timestamps in log output
- Format: [2026-01-15 19:45:23.456] [LEVEL] message
- Shows full date, time, and milliseconds for precise log tracking
Phoenix UI Enhancements:
- Added format_datetime/1 - Full date/time with timezone
- Added format_date/1 - Short date format
- Added format_last_seen_with_date/1 - Relative time with full date
- Updated agent show page to display full timestamps in 'Last Seen' card
- Added comprehensive Timestamps section showing:
- Created date (inserted_at)
- Last Updated date (updated_at)
- Last Seen date (last_seen_at with heartbeat context)
- Last IP Address
- Updated agent index page to show full datetime alongside relative time
All timestamps now include both human-readable relative times ('5m ago')
and precise absolute dates for accurate record keeping and debugging.
124 lines
3.1 KiB
Rust
124 lines
3.1 KiB
Rust
mod api_client;
|
|
mod buffer;
|
|
mod config;
|
|
mod health;
|
|
mod metrics;
|
|
mod poller;
|
|
mod proto;
|
|
mod snmp;
|
|
mod version;
|
|
|
|
use chrono::Local;
|
|
use clap::Parser;
|
|
use log::{info, LevelFilter, Metadata, Record};
|
|
use poller::Scheduler;
|
|
use std::env;
|
|
|
|
/// Minimal logger that writes to stderr with timestamps
|
|
struct SimpleLogger {
|
|
level: LevelFilter,
|
|
}
|
|
|
|
impl log::Log for SimpleLogger {
|
|
fn enabled(&self, metadata: &Metadata) -> bool {
|
|
metadata.level() <= self.level
|
|
}
|
|
|
|
fn log(&self, record: &Record) {
|
|
if self.enabled(record.metadata()) {
|
|
let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
|
|
eprintln!("[{}] [{}] {}", timestamp, record.level(), record.args());
|
|
}
|
|
}
|
|
|
|
fn flush(&self) {}
|
|
}
|
|
|
|
fn init_logger() {
|
|
let level = env::var("RUST_LOG")
|
|
.unwrap_or_else(|_| "info".to_string())
|
|
.parse::<LevelFilter>()
|
|
.unwrap_or(LevelFilter::Info);
|
|
|
|
let logger = SimpleLogger { level };
|
|
log::set_boxed_logger(Box::new(logger))
|
|
.map(|()| log::set_max_level(level))
|
|
.ok();
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
#[command(name = "towerops-agent")]
|
|
#[command(about = "Towerops remote SNMP polling agent", long_about = None)]
|
|
struct Args {
|
|
/// API URL (e.g., https://app.towerops.com)
|
|
#[arg(long, env = "TOWEROPS_API_URL")]
|
|
api_url: String,
|
|
|
|
/// Agent authentication token
|
|
#[arg(long, env = "TOWEROPS_AGENT_TOKEN")]
|
|
token: String,
|
|
|
|
/// Configuration refresh interval in seconds
|
|
#[arg(long, env = "CONFIG_REFRESH_SECONDS", default_value = "300")]
|
|
config_refresh_seconds: u64,
|
|
|
|
/// Database path for metrics buffering
|
|
#[arg(long, env = "DATABASE_PATH", default_value = "/data/towerops-agent.db")]
|
|
database_path: String,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
// Initialize logging
|
|
init_logger();
|
|
|
|
let args = Args::parse();
|
|
|
|
info!("Towerops agent starting");
|
|
|
|
// Check for newer Docker image version
|
|
version::check_for_updates();
|
|
|
|
info!("API URL: {}", args.api_url);
|
|
info!(
|
|
"Config refresh interval: {} seconds",
|
|
args.config_refresh_seconds
|
|
);
|
|
info!("Database path: {}", args.database_path);
|
|
|
|
// Initialize components
|
|
let api_client = match api_client::ApiClient::new(args.api_url, args.token) {
|
|
Ok(client) => client,
|
|
Err(e) => {
|
|
eprintln!("Failed to create API client: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
|
|
let storage = match buffer::Storage::new(args.database_path) {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
eprintln!("Failed to create storage: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
|
|
// Start health endpoint server
|
|
let health_server = health::HealthServer::new(storage.clone());
|
|
health_server.start(8080);
|
|
|
|
let snmp_client = snmp::SnmpClient::new();
|
|
|
|
// Create and run scheduler
|
|
let mut scheduler = Scheduler::new(
|
|
api_client,
|
|
storage,
|
|
snmp_client,
|
|
args.config_refresh_seconds,
|
|
);
|
|
|
|
if let Err(e) = scheduler.run().await {
|
|
eprintln!("Scheduler error: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
}
|