mirror of
https://github.com/YGGverse/htcount.git
synced 2026-04-01 17:45:28 +00:00
initial commit
This commit is contained in:
parent
c8d4947382
commit
f56ebb6877
9 changed files with 291 additions and 0 deletions
48
src/argument.rs
Normal file
48
src/argument.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct Argument {
|
||||
/// Debug level
|
||||
///
|
||||
/// * `i` - info
|
||||
/// * `d` - detailed
|
||||
#[arg(short, long, default_value_t = String::from("i"))]
|
||||
pub debug: String,
|
||||
|
||||
/// Log format for given `source`
|
||||
///
|
||||
/// * `nginx`
|
||||
#[arg(short, long, default_value_t = String::from("nginx"))]
|
||||
pub format: String,
|
||||
|
||||
/// Export results to JSON file (e.g. `/path/to/stats.json`)
|
||||
#[arg(long)]
|
||||
pub export_json: Option<String>,
|
||||
|
||||
/// Export results to SVG file (e.g. `/path/to/badge.svg`)
|
||||
///
|
||||
/// * use `{hits}` / `{hosts}` pattern to replace parsed values
|
||||
#[arg(long)]
|
||||
pub export_svg: Option<String>,
|
||||
|
||||
/// Use custom SVG file template with `{hits}` / `{hosts}` placeholders
|
||||
#[arg(long, default_value_t = String::from("default/counter.svg"))]
|
||||
pub template_svg: String,
|
||||
|
||||
/// Expected memory index capacity
|
||||
#[arg(short, long, default_value_t = 100)]
|
||||
pub capacity: usize,
|
||||
|
||||
/// Exclude host(s) from index
|
||||
#[arg(short, long)]
|
||||
pub ignore_host: Vec<String>,
|
||||
|
||||
/// Access log source (e.g. `/var/nginx/access.log`)
|
||||
#[arg(short, long)]
|
||||
pub source: String,
|
||||
|
||||
/// Update delay in seconds
|
||||
#[arg(short, long, default_value_t = 300)]
|
||||
pub update: u64,
|
||||
}
|
||||
10
src/debug.rs
Normal file
10
src/debug.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
pub fn info(message: String) {
|
||||
println!("[{}] [info] {message}", now())
|
||||
}
|
||||
|
||||
fn now() -> u128 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis()
|
||||
}
|
||||
94
src/main.rs
Normal file
94
src/main.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
mod argument;
|
||||
mod debug;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::File,
|
||||
io::{BufRead, BufReader, Write},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
let argument = {
|
||||
use clap::Parser;
|
||||
argument::Argument::parse()
|
||||
};
|
||||
|
||||
// calculate debug level once
|
||||
let is_debug_i = argument.debug.contains("i");
|
||||
let is_debug_d = argument.debug.contains("d");
|
||||
|
||||
if !matches!(argument.format.to_lowercase().as_str(), "nginx") {
|
||||
todo!("Format `{}` yet not supported!", argument.format)
|
||||
}
|
||||
|
||||
if is_debug_i {
|
||||
debug::info("Crawler started".into());
|
||||
}
|
||||
|
||||
loop {
|
||||
if is_debug_i {
|
||||
debug::info("Index queue begin...".into());
|
||||
}
|
||||
|
||||
let file = File::open(&argument.source)?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
let mut index: HashMap<String, usize> = HashMap::with_capacity(argument.capacity);
|
||||
|
||||
for line in reader.lines() {
|
||||
let host = line?
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.map(|s| s.into())
|
||||
.unwrap_or_default();
|
||||
|
||||
if argument.ignore_host.contains(&host) {
|
||||
if is_debug_d {
|
||||
debug::info(format!("Host `{host}` ignored by settings"))
|
||||
}
|
||||
continue;
|
||||
}
|
||||
index.entry(host).and_modify(|c| *c += 1).or_insert(1);
|
||||
}
|
||||
|
||||
let hosts = index.len();
|
||||
let hits: usize = index.values().sum();
|
||||
|
||||
if is_debug_i {
|
||||
debug::info(format!(
|
||||
"Index queue completed:\n{}\n\thosts: {} / hits: {}, await {} seconds to continue...",
|
||||
if is_debug_d {
|
||||
let mut b = Vec::with_capacity(hosts);
|
||||
for (host, count) in &index {
|
||||
b.push(format!("\t{} ({})", host, count))
|
||||
}
|
||||
b.join("\n")
|
||||
} else {
|
||||
"".into()
|
||||
},
|
||||
hosts,
|
||||
hits,
|
||||
argument.update,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(ref p) = argument.export_json {
|
||||
let mut f = File::create(p)?;
|
||||
f.write_all(format!("{{\"hosts\":{hosts},\"hits\":{hits}}}").as_bytes())?;
|
||||
}
|
||||
|
||||
if let Some(ref p) = argument.export_svg {
|
||||
let t = std::fs::read_to_string(&argument.template_svg)?;
|
||||
let mut f = File::create(p)?;
|
||||
f.write_all(
|
||||
t.replace("{hosts}", &hosts.to_string())
|
||||
.replace("{hits}", &hits.to_string())
|
||||
.as_bytes(),
|
||||
)?;
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_secs(argument.update));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue