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;
9pub(crate) mod access_sink;
10pub mod tail;
11
12pub struct Log;
18
19impl Log {
20 pub fn request_response(request: &Request, response: &Response, peer_addr: &SocketAddr) -> String {
21 let mut request_headers = "".to_string();
22 for header in &request.headers {
23 if &header.name.chars().count() > &0 {
24 request_headers = [
25 request_headers,
26 "\n ".to_string(),
27 header.name.to_string(),
28 ": ".to_string(),
29 header.value.to_string()
30 ].join("");
31 }
32 }
33
34 let mut response_headers = "".to_string();
35 for header in &response.headers {
36 if &header.name.chars().count() > &0 {
37 response_headers = [
38 response_headers,
39 "\n ".to_string(),
40 header.name.to_string(),
41 ": ".to_string(),
42 header.value.to_string()
43 ].join("");
44 }
45 }
46
47 let mut response_body_length = 0;
48 let mut response_body_parts_number = 0;
49 for content_range in &response.content_range_list {
50 let boxed_parse = content_range.size.parse::<i32>();
51 if boxed_parse.is_ok() {
52 response_body_length += boxed_parse.unwrap();
53 response_body_parts_number += 1;
54 }
55 }
56
57 let current_thread = thread::current();
58 let thread_id = current_thread.name().unwrap();
59
60 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",
61 thread_id,
62 peer_addr,
63 &request.http_version,
64 &request.method,
65 &request.request_uri,
66 request_headers,
67 request.body.len(),
68
69 &response.status_code,
70 &response.reason_phrase,
71 response_headers,
72 response_body_parts_number,
73 response_body_length);
74
75 log_request_response
76 }
77
78 pub fn usage_information() -> String {
79 let mut log = "Usage:\n\n".to_string();
80 let command_line_arg_list = CommandLineArgument::get_command_line_arg_list();
81 for arg in command_line_arg_list {
82 let flags = if arg.short_form.is_empty() {
83 format!("--{}", arg.long_form)
84 } else {
85 format!("-{} or --{}", arg.short_form, arg.long_form)
86 };
87 let argument_info = format!(" {} environment variable\n {} as command line line argument\n {}\n\n", arg.environment_variable, flags, arg._hint.unwrap());
88 log = [log, argument_info].join("");
89 }
90 log = [log, "End of usage section\n\n".to_string()].join("");
91 log
92 }
93
94 pub fn current_user() -> String {
100 ["USER", "LOGNAME", "USERNAME"]
101 .iter()
102 .find_map(|var| std::env::var(var).ok().filter(|v| !v.trim().is_empty()))
103 .unwrap_or_else(|| "unknown".to_string())
104 }
105
106 pub fn info(name: &str) -> String {
107 let mut log = format!("{}\n", name).to_string();
108 const VERSION: &str = env!("CARGO_PKG_VERSION");
109 const AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
110 const REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
111 const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
112 const RUST_VERSION: &str = env!("CARGO_PKG_RUST_VERSION");
113 const LICENSE: &str = env!("CARGO_PKG_LICENSE");
114 let user = Log::current_user();
115 let working_directory = crate::app::controller::static_resource::working_directory().to_string();
116
117 let version = format!("Version: {}\n", VERSION);
118 log = [log, version].join("");
119
120 let authors = format!("Authors: {}\n", AUTHORS);
121 log = [log, authors].join("");
122
123 let repository = format!("Repository: {}\n", REPOSITORY);
124 log = [log, repository].join("");
125
126 let description = format!("Description: {}\n", DESCRIPTION);
127 log = [log, description].join("");
128
129 let rust_version = format!("Rust Version: {}\n", RUST_VERSION);
130 log = [log, rust_version].join("");
131
132 let license = format!("License: {}\n", LICENSE);
133 log = [log, license].join("");
134
135 let license = format!("User: {}\n", user);
136 log = [log, license].join("");
137
138 let license = format!("Working Directory: {}\n", working_directory);
139 log = [log, license].join("");
140
141 log
142 }
143
144 pub fn json(request: &Request, response: &Response, peer_addr: &SocketAddr) -> String {
149 let mut line = Vec::with_capacity(160);
150 Log::write_json(&mut line, request, response, peer_addr);
151 bytes_to_string(line)
152 }
153
154 fn write_json(out: &mut Vec<u8>, request: &Request, response: &Response, peer_addr: &SocketAddr) {
156 use std::io::Write;
157 let body_size: usize = response.content_range_list.iter().map(|cr| cr.body.len()).sum();
158 out.extend_from_slice(b"{\"time\":\"");
159 with_timestamps(|iso, _| out.extend_from_slice(iso.as_bytes()));
160 out.extend_from_slice(b"\",\"remote_addr\":\"");
161 let _ = write!(out, "{}", peer_addr.ip());
162 out.extend_from_slice(b"\",\"method\":\"");
163 push_json_escaped(out, &request.method);
164 out.extend_from_slice(b"\",\"path\":\"");
165 push_json_escaped(out, &request.request_uri);
166 out.extend_from_slice(b"\",\"protocol\":\"");
167 push_json_escaped(out, &request.http_version);
168 out.extend_from_slice(b"\",\"status\":");
169 let _ = write!(out, "{}", response.status_code);
170 out.extend_from_slice(b",\"bytes\":");
171 out.extend_from_slice(crate::ext::decimal::Decimal::new(body_size as u128).as_str().as_bytes());
172 out.push(b'}');
173 }
174
175 pub fn log_access(request: &Request, response: &Response, peer_addr: &SocketAddr) {
183 let config = crate::server_config::ServerConfig::current();
184 let format = &config.log_format;
185 if format.eq_ignore_ascii_case("off") || format.eq_ignore_ascii_case("none") {
186 return;
187 }
188 let json = format == "json";
189 access_sink::write_line(|out| {
190 if json {
191 Log::write_json(out, request, response, peer_addr)
192 } else {
193 Log::write_combined(out, request, response, peer_addr)
194 }
195 });
196 }
197
198 pub fn reopen_access_log_on_sigusr1() {
202 access_sink::install_reopen_signal();
203 }
204
205 pub fn reopen_access_log() {
208 access_sink::reopen();
209 }
210
211 pub fn flush_access_log() {
214 access_sink::flush();
215 }
216
217 pub fn access_log_line(request: &Request, response: &Response, peer_addr: &SocketAddr) -> Option<String> {
223 let config = crate::server_config::ServerConfig::current();
224 let format = &config.log_format;
225 if format.eq_ignore_ascii_case("off") || format.eq_ignore_ascii_case("none") {
226 return None;
227 }
228 Some(if format == "json" {
229 Log::json(request, response, peer_addr)
230 } else {
231 Log::combined(request, response, peer_addr)
232 })
233 }
234
235 fn format_iso8601(secs: u64) -> String {
236 let sec = secs % 60;
237 let min = (secs / 60) % 60;
238 let hour = (secs / 3600) % 24;
239 let days = secs / 86400;
240 let (year, month, day) = Log::days_to_ymd(days);
241 format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", year, month, day, hour, min, sec)
242 }
243
244 pub fn combined(request: &Request, response: &Response, peer_addr: &SocketAddr) -> String {
247 let mut line = Vec::with_capacity(128);
248 Log::write_combined(&mut line, request, response, peer_addr);
249 bytes_to_string(line)
250 }
251
252 fn write_combined(out: &mut Vec<u8>, request: &Request, response: &Response, peer_addr: &SocketAddr) {
254 use std::io::Write;
255 let body_size: usize = response.content_range_list.iter().map(|cr| cr.body.len()).sum();
256 let _ = write!(out, "{}", peer_addr.ip());
257 out.extend_from_slice(b" - - [");
258 with_timestamps(|_, clf| out.extend_from_slice(clf.as_bytes()));
259 out.extend_from_slice(b"] \"");
260 out.extend_from_slice(request.method.as_bytes());
261 out.push(b' ');
262 out.extend_from_slice(request.request_uri.as_bytes());
263 out.push(b' ');
264 out.extend_from_slice(request.http_version.as_bytes());
265 out.extend_from_slice(b"\" ");
266 let _ = write!(out, "{}", response.status_code);
267 out.push(b' ');
268 if body_size > 0 {
269 out.extend_from_slice(crate::ext::decimal::Decimal::new(body_size as u128).as_str().as_bytes());
270 } else {
271 out.push(b'-');
272 }
273 }
274
275 fn format_clf_timestamp(secs: u64) -> String {
276 let sec = secs % 60;
277 let min = (secs / 60) % 60;
278 let hour = (secs / 3600) % 24;
279 let days = secs / 86400;
280 let (year, month, day) = Log::days_to_ymd(days);
281 const MONTHS: [&str; 12] = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
282 format!("{:02}/{}/{:04}:{:02}:{:02}:{:02} +0000",
283 day, MONTHS[(month - 1) as usize], year, hour, min, sec)
284 }
285
286 fn days_to_ymd(days: u64) -> (u64, u64, u64) {
287 let z = days + 719468;
288 let era = z / 146097;
289 let doe = z % 146097;
290 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
291 let y = yoe + era * 400;
292 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
293 let mp = (5 * doy + 2) / 153;
294 let d = doy - (153 * mp + 2) / 5 + 1;
295 let m = if mp < 10 { mp + 3 } else { mp - 9 };
296 let y = if m <= 2 { y + 1 } else { y };
297 (y, m, d)
298 }
299
300 pub fn server_url_thread_count(protocol: &str, bind_addr: &String, thread_count: i32) -> String {
301 let url = format!("Server is up and running at: {}://{}\n", protocol, &bind_addr);
302 let thread_count = format!("Spawned {} thread(s) to handle incoming requests\n", thread_count);
303 [url, thread_count].join("")
304 }
305}
306pub(crate) fn iso8601_now(use_it: impl FnOnce(&str)) {
309 with_timestamps(|iso, _| use_it(iso));
310}
311
312fn with_timestamps(use_them: impl FnOnce(&str, &str)) {
315 use std::cell::RefCell;
316 use std::time::{SystemTime, UNIX_EPOCH};
317 thread_local! {
318 static CACHED: RefCell<(u64, String, String)> = const { RefCell::new((u64::MAX, String::new(), String::new())) };
319 }
320 let secs = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
321 CACHED.with(|cached| {
322 let mut cached = cached.borrow_mut();
323 if cached.0 != secs {
324 *cached = (secs, Log::format_iso8601(secs), Log::format_clf_timestamp(secs));
325 }
326 use_them(&cached.1, &cached.2);
327 });
328}
329
330fn push_json_escaped(out: &mut Vec<u8>, s: &str) {
333 for b in s.bytes() {
334 match b {
335 b'\\' => out.extend_from_slice(b"\\\\"),
336 b'"' => out.extend_from_slice(b"\\\""),
337 b'\n' => out.extend_from_slice(b"\\n"),
338 b'\r' => out.extend_from_slice(b"\\r"),
339 _ => out.push(b),
340 }
341 }
342}
343
344fn bytes_to_string(bytes: Vec<u8>) -> String {
346 String::from_utf8(bytes).unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned())
347}