1use std::net::SocketAddr;
2use std::thread;
3use crate::entry_point::command_line_args::CommandLineArgument;
4use crate::request::Request;
5use crate::response::Response;
6
7#[cfg(test)]
8mod tests;
9#[doc(hidden)]
10pub mod access_sink;
11pub mod tail;
12
13pub struct Log;
19
20static ACCESS_OBSERVER: std::sync::OnceLock<fn(&Request, &Response, std::net::IpAddr)> = std::sync::OnceLock::new();
23
24#[doc(hidden)]
26pub fn set_access_observer(observe: fn(&Request, &Response, std::net::IpAddr)) {
27 let _ = ACCESS_OBSERVER.set(observe);
28}
29
30impl Log {
31 pub fn request_response(request: &Request, response: &Response, peer_addr: &SocketAddr) -> String {
32 let mut request_headers = "".to_string();
33 for header in &request.headers {
34 if &header.name.chars().count() > &0 {
35 request_headers = [
36 request_headers,
37 "\n ".to_string(),
38 header.name.to_string(),
39 ": ".to_string(),
40 header.value.to_string()
41 ].join("");
42 }
43 }
44
45 let mut response_headers = "".to_string();
46 for header in &response.headers {
47 if &header.name.chars().count() > &0 {
48 response_headers = [
49 response_headers,
50 "\n ".to_string(),
51 header.name.to_string(),
52 ": ".to_string(),
53 header.value.to_string()
54 ].join("");
55 }
56 }
57
58 let mut response_body_length = 0;
59 let mut response_body_parts_number = 0;
60 for content_range in &response.content_range_list {
61 let boxed_parse = content_range.size.parse::<i32>();
62 if boxed_parse.is_ok() {
63 response_body_length += boxed_parse.unwrap();
64 response_body_parts_number += 1;
65 }
66 }
67
68 let current_thread = thread::current();
69 let thread_id = current_thread.name().unwrap();
70
71 let log_request_response = format!("\n\nRequest (thread id: {} peer address is {}):\n {} {} {} {}\n Body: {} byte(s) total (including default initialization vector)\nEnd of Request\nResponse:\n {} {} {}\n\n Body: {} part(s), {} byte(s) total\nEnd of Response",
72 thread_id,
73 peer_addr,
74 &request.http_version,
75 &request.method,
76 &request.request_uri,
77 request_headers,
78 request.body.len(),
79
80 &response.status_code,
81 &response.reason_phrase,
82 response_headers,
83 response_body_parts_number,
84 response_body_length);
85
86 log_request_response
87 }
88
89 pub fn usage_information() -> String {
90 let mut log = "Usage:\n\n".to_string();
91 let command_line_arg_list = CommandLineArgument::get_command_line_arg_list();
92 for arg in command_line_arg_list {
93 let flags = if arg.short_form.is_empty() {
94 format!("--{}", arg.long_form)
95 } else {
96 format!("-{} or --{}", arg.short_form, arg.long_form)
97 };
98 let argument_info = format!(" {} environment variable\n {} as command line line argument\n {}\n\n", arg.environment_variable, flags, arg._hint.unwrap());
99 log = [log, argument_info].join("");
100 }
101 log = [log, "End of usage section\n\n".to_string()].join("");
102 log
103 }
104
105 pub fn current_user() -> String {
111 ["USER", "LOGNAME", "USERNAME"]
112 .iter()
113 .find_map(|var| std::env::var(var).ok().filter(|v| !v.trim().is_empty()))
114 .unwrap_or_else(|| "unknown".to_string())
115 }
116
117 pub fn info(name: &str) -> String {
118 let mut log = format!("{}\n", name).to_string();
119 const VERSION: &str = env!("CARGO_PKG_VERSION");
120 const AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
121 const REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
122 const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
123 const RUST_VERSION: &str = env!("CARGO_PKG_RUST_VERSION");
124 const LICENSE: &str = env!("CARGO_PKG_LICENSE");
125 let user = Log::current_user();
126 let working_directory = crate::app::controller::static_resource::working_directory().to_string();
127
128 let version = format!("Version: {}\n", VERSION);
129 log = [log, version].join("");
130
131 let authors = format!("Authors: {}\n", AUTHORS);
132 log = [log, authors].join("");
133
134 let repository = format!("Repository: {}\n", REPOSITORY);
135 log = [log, repository].join("");
136
137 let description = format!("Description: {}\n", DESCRIPTION);
138 log = [log, description].join("");
139
140 let rust_version = format!("Rust Version: {}\n", RUST_VERSION);
141 log = [log, rust_version].join("");
142
143 let license = format!("License: {}\n", LICENSE);
144 log = [log, license].join("");
145
146 let license = format!("User: {}\n", user);
147 log = [log, license].join("");
148
149 let license = format!("Working Directory: {}\n", working_directory);
150 log = [log, license].join("");
151
152 log
153 }
154
155 pub fn json(request: &Request, response: &Response, peer_addr: &SocketAddr) -> String {
162 let mut line = Vec::with_capacity(160);
163 Log::write_json(&mut line, request, response, peer_addr);
164 bytes_to_string(line)
165 }
166
167 fn write_json(out: &mut Vec<u8>, request: &Request, response: &Response, peer_addr: &SocketAddr) {
169 use std::io::Write;
170 let body_size: usize = response.content_range_list.iter().map(|cr| cr.body.len()).sum();
171 out.extend_from_slice(b"{\"time\":\"");
172 with_timestamps(|iso, _| out.extend_from_slice(iso.as_bytes()));
173 out.extend_from_slice(b"\",\"remote_addr\":\"");
174 let _ = write!(out, "{}", peer_addr.ip());
175 out.extend_from_slice(b"\",\"method\":\"");
176 push_json_escaped(out, &request.method);
177 out.extend_from_slice(b"\",\"path\":\"");
178 push_json_escaped(out, &request.request_uri);
179 out.extend_from_slice(b"\",\"protocol\":\"");
180 push_json_escaped(out, &request.http_version);
181 out.extend_from_slice(b"\",\"status\":");
182 let _ = write!(out, "{}", response.status_code);
183 out.extend_from_slice(b",\"bytes\":");
184 out.extend_from_slice(crate::ext::decimal::Decimal::new(body_size as u128).as_str().as_bytes());
185 for (key, name) in [(&b",\"host\":\""[..], "host"), (&b",\"user_agent\":\""[..], "user-agent"), (&b",\"referer\":\""[..], "referer")] {
187 if let Some(header) = request.header(name).filter(|h| !h.value.is_empty()) {
188 out.extend_from_slice(key);
189 push_json_escaped(out, &header.value);
190 out.push(b'"');
191 }
192 }
193 out.push(b'}');
194 }
195
196 pub fn log_access(request: &Request, response: &Response, peer_addr: &SocketAddr) {
204 let client;
207 let peer_addr = if request.header("x-forwarded-for").is_some() || request.header("forwarded").is_some() {
208 client = crate::ip_filter::client_ip(request, &peer_addr.ip().to_string())
209 .parse::<std::net::IpAddr>()
210 .map_or(*peer_addr, |ip| SocketAddr::new(ip, peer_addr.port()));
211 &client
212 } else {
213 peer_addr
214 };
215 if let Some(observe) = ACCESS_OBSERVER.get() {
217 observe(request, response, peer_addr.ip());
218 }
219 let config = crate::server_config::ServerConfig::current();
220 let format = &config.log_format;
221 if format.eq_ignore_ascii_case("off") || format.eq_ignore_ascii_case("none") {
222 return;
223 }
224 let json = format == "json";
225 access_sink::write_line(|out| {
226 if json {
227 Log::write_json(out, request, response, peer_addr)
228 } else {
229 Log::write_combined(out, request, response, peer_addr)
230 }
231 });
232 }
233
234 pub fn reopen_access_log_on_sigusr1() {
238 access_sink::install_reopen_signal();
239 }
240
241 pub fn reopen_access_log() {
244 access_sink::reopen();
245 }
246
247 pub fn flush_access_log() {
250 access_sink::flush();
251 }
252
253 pub fn access_log_line(request: &Request, response: &Response, peer_addr: &SocketAddr) -> Option<String> {
259 let config = crate::server_config::ServerConfig::current();
260 let format = &config.log_format;
261 if format.eq_ignore_ascii_case("off") || format.eq_ignore_ascii_case("none") {
262 return None;
263 }
264 Some(if format == "json" {
265 Log::json(request, response, peer_addr)
266 } else {
267 Log::combined(request, response, peer_addr)
268 })
269 }
270
271 fn format_iso8601(secs: u64) -> String {
272 let sec = secs % 60;
273 let min = (secs / 60) % 60;
274 let hour = (secs / 3600) % 24;
275 let days = secs / 86400;
276 let (year, month, day) = Log::days_to_ymd(days);
277 format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", year, month, day, hour, min, sec)
278 }
279
280 pub fn combined(request: &Request, response: &Response, peer_addr: &SocketAddr) -> String {
283 let mut line = Vec::with_capacity(128);
284 Log::write_combined(&mut line, request, response, peer_addr);
285 bytes_to_string(line)
286 }
287
288 fn write_combined(out: &mut Vec<u8>, request: &Request, response: &Response, peer_addr: &SocketAddr) {
290 use std::io::Write;
291 let body_size: usize = response.content_range_list.iter().map(|cr| cr.body.len()).sum();
292 let _ = write!(out, "{}", peer_addr.ip());
293 out.extend_from_slice(b" - - [");
294 with_timestamps(|_, clf| out.extend_from_slice(clf.as_bytes()));
295 out.extend_from_slice(b"] \"");
296 out.extend_from_slice(request.method.as_bytes());
297 out.push(b' ');
298 out.extend_from_slice(request.request_uri.as_bytes());
299 out.push(b' ');
300 out.extend_from_slice(request.http_version.as_bytes());
301 out.extend_from_slice(b"\" ");
302 let _ = write!(out, "{}", response.status_code);
303 out.push(b' ');
304 if body_size > 0 {
305 out.extend_from_slice(crate::ext::decimal::Decimal::new(body_size as u128).as_str().as_bytes());
306 } else {
307 out.push(b'-');
308 }
309 }
310
311 fn format_clf_timestamp(secs: u64) -> String {
312 let sec = secs % 60;
313 let min = (secs / 60) % 60;
314 let hour = (secs / 3600) % 24;
315 let days = secs / 86400;
316 let (year, month, day) = Log::days_to_ymd(days);
317 const MONTHS: [&str; 12] = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
318 format!("{:02}/{}/{:04}:{:02}:{:02}:{:02} +0000",
319 day, MONTHS[(month - 1) as usize], year, hour, min, sec)
320 }
321
322 fn days_to_ymd(days: u64) -> (u64, u64, u64) {
323 let z = days + 719468;
324 let era = z / 146097;
325 let doe = z % 146097;
326 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
327 let y = yoe + era * 400;
328 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
329 let mp = (5 * doy + 2) / 153;
330 let d = doy - (153 * mp + 2) / 5 + 1;
331 let m = if mp < 10 { mp + 3 } else { mp - 9 };
332 let y = if m <= 2 { y + 1 } else { y };
333 (y, m, d)
334 }
335
336 pub fn server_url_thread_count(protocol: &str, bind_addr: &String, thread_count: i32) -> String {
337 let url = format!("Server is up and running at: {}://{}\n", protocol, &bind_addr);
338 let thread_count = format!("Spawned {} thread(s) to handle incoming requests\n", thread_count);
339 [url, thread_count].join("")
340 }
341}
342#[doc(hidden)]
345pub fn iso8601_now(use_it: impl FnOnce(&str)) {
346 with_timestamps(|iso, _| use_it(iso));
347}
348
349fn with_timestamps(use_them: impl FnOnce(&str, &str)) {
352 use std::cell::RefCell;
353 use std::time::{SystemTime, UNIX_EPOCH};
354 thread_local! {
355 static CACHED: RefCell<(u64, String, String)> = const { RefCell::new((u64::MAX, String::new(), String::new())) };
356 }
357 let secs = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
358 CACHED.with(|cached| {
359 let mut cached = cached.borrow_mut();
360 if cached.0 != secs {
361 *cached = (secs, Log::format_iso8601(secs), Log::format_clf_timestamp(secs));
362 }
363 use_them(&cached.1, &cached.2);
364 });
365}
366
367fn push_json_escaped(out: &mut Vec<u8>, s: &str) {
370 for b in s.bytes() {
371 match b {
372 b'\\' => out.extend_from_slice(b"\\\\"),
373 b'"' => out.extend_from_slice(b"\\\""),
374 b'\n' => out.extend_from_slice(b"\\n"),
375 b'\r' => out.extend_from_slice(b"\\r"),
376 _ => out.push(b),
377 }
378 }
379}
380
381fn bytes_to_string(bytes: Vec<u8>) -> String {
383 String::from_utf8(bytes).unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned())
384}