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
10pub struct Log;
16
17impl Log {
18 pub fn request_response(request: &Request, response: &Response, peer_addr: &SocketAddr) -> String {
19 let mut request_headers = "".to_string();
20 for header in &request.headers {
21 if &header.name.chars().count() > &0 {
22 request_headers = [
23 request_headers,
24 "\n ".to_string(),
25 header.name.to_string(),
26 ": ".to_string(),
27 header.value.to_string()
28 ].join("");
29 }
30 }
31
32 let mut response_headers = "".to_string();
33 for header in &response.headers {
34 if &header.name.chars().count() > &0 {
35 response_headers = [
36 response_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_body_length = 0;
46 let mut response_body_parts_number = 0;
47 for content_range in &response.content_range_list {
48 let boxed_parse = content_range.size.parse::<i32>();
49 if boxed_parse.is_ok() {
50 response_body_length += boxed_parse.unwrap();
51 response_body_parts_number += 1;
52 }
53 }
54
55 let current_thread = thread::current();
56 let thread_id = current_thread.name().unwrap();
57
58 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",
59 thread_id,
60 peer_addr,
61 &request.http_version,
62 &request.method,
63 &request.request_uri,
64 request_headers,
65 request.body.len(),
66
67 &response.status_code,
68 &response.reason_phrase,
69 response_headers,
70 response_body_parts_number,
71 response_body_length);
72
73 log_request_response
74 }
75
76 pub fn usage_information() -> String {
77 let mut log = "Usage:\n\n".to_string();
78 let command_line_arg_list = CommandLineArgument::get_command_line_arg_list();
79 for arg in command_line_arg_list {
80 let flags = if arg.short_form.is_empty() {
81 format!("--{}", arg.long_form)
82 } else {
83 format!("-{} or --{}", arg.short_form, arg.long_form)
84 };
85 let argument_info = format!(" {} environment variable\n {} as command line line argument\n {}\n\n", arg.environment_variable, flags, arg._hint.unwrap());
86 log = [log, argument_info].join("");
87 }
88 log = [log, "End of usage section\n\n".to_string()].join("");
89 log
90 }
91
92 pub fn current_user() -> String {
98 ["USER", "LOGNAME", "USERNAME"]
99 .iter()
100 .find_map(|var| std::env::var(var).ok().filter(|v| !v.trim().is_empty()))
101 .unwrap_or_else(|| "unknown".to_string())
102 }
103
104 pub fn info(name: &str) -> String {
105 let mut log = format!("{}\n", name).to_string();
106 const VERSION: &str = env!("CARGO_PKG_VERSION");
107 const AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
108 const REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
109 const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
110 const RUST_VERSION: &str = env!("CARGO_PKG_RUST_VERSION");
111 const LICENSE: &str = env!("CARGO_PKG_LICENSE");
112 let user = Log::current_user();
113 let working_directory = crate::app::controller::static_resource::working_directory().to_string();
114
115 let version = format!("Version: {}\n", VERSION);
116 log = [log, version].join("");
117
118 let authors = format!("Authors: {}\n", AUTHORS);
119 log = [log, authors].join("");
120
121 let repository = format!("Repository: {}\n", REPOSITORY);
122 log = [log, repository].join("");
123
124 let description = format!("Description: {}\n", DESCRIPTION);
125 log = [log, description].join("");
126
127 let rust_version = format!("Rust Version: {}\n", RUST_VERSION);
128 log = [log, rust_version].join("");
129
130 let license = format!("License: {}\n", LICENSE);
131 log = [log, license].join("");
132
133 let license = format!("User: {}\n", user);
134 log = [log, license].join("");
135
136 let license = format!("Working Directory: {}\n", working_directory);
137 log = [log, license].join("");
138
139 log
140 }
141
142 pub fn json(request: &Request, response: &Response, peer_addr: &SocketAddr) -> String {
147 use std::time::{SystemTime, UNIX_EPOCH};
148
149 let body_size: usize = response.content_range_list.iter()
150 .map(|cr| cr.body.len())
151 .sum();
152
153 let secs = SystemTime::now()
154 .duration_since(UNIX_EPOCH)
155 .unwrap_or_default()
156 .as_secs();
157
158 let timestamp = Log::format_iso8601(secs);
159
160 fn escape(s: &str) -> String {
161 s.replace('\\', "\\\\")
162 .replace('"', "\\\"")
163 .replace('\n', "\\n")
164 .replace('\r', "\\r")
165 }
166
167 format!(
168 "{{\"time\":\"{}\",\"remote_addr\":\"{}\",\"method\":\"{}\",\"path\":\"{}\",\"protocol\":\"{}\",\"status\":{},\"bytes\":{}}}",
169 timestamp,
170 peer_addr.ip(),
171 escape(&request.method),
172 escape(&request.request_uri),
173 escape(&request.http_version),
174 response.status_code,
175 body_size,
176 )
177 }
178
179 pub fn log_access(request: &Request, response: &Response, peer_addr: &SocketAddr) {
183 if let Some(line) = Log::access_log_line(request, response, peer_addr) {
184 println!("{}", line);
185 }
186 }
187
188 pub fn access_log_line(request: &Request, response: &Response, peer_addr: &SocketAddr) -> Option<String> {
194 let config = crate::server_config::ServerConfig::current();
195 let format = &config.log_format;
196 if format.eq_ignore_ascii_case("off") || format.eq_ignore_ascii_case("none") {
197 return None;
198 }
199 Some(if format == "json" {
200 Log::json(request, response, peer_addr)
201 } else {
202 Log::combined(request, response, peer_addr)
203 })
204 }
205
206 fn format_iso8601(secs: u64) -> String {
207 let sec = secs % 60;
208 let min = (secs / 60) % 60;
209 let hour = (secs / 3600) % 24;
210 let days = secs / 86400;
211 let (year, month, day) = Log::days_to_ymd(days);
212 format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", year, month, day, hour, min, sec)
213 }
214
215 pub fn combined(request: &Request, response: &Response, peer_addr: &SocketAddr) -> String {
218 use std::time::{SystemTime, UNIX_EPOCH};
219
220 let body_size: usize = response.content_range_list.iter()
221 .map(|cr| cr.body.len())
222 .sum();
223
224 let secs = SystemTime::now()
225 .duration_since(UNIX_EPOCH)
226 .unwrap_or_default()
227 .as_secs();
228
229 let timestamp = Log::format_clf_timestamp(secs);
230
231 let body_str = if body_size > 0 { body_size.to_string() } else { "-".to_string() };
232
233 format!("{} - - [{}] \"{} {} {}\" {} {}",
234 peer_addr.ip(),
235 timestamp,
236 request.method,
237 request.request_uri,
238 request.http_version,
239 response.status_code,
240 body_str,
241 )
242 }
243
244 fn format_clf_timestamp(secs: u64) -> String {
245 let sec = secs % 60;
246 let min = (secs / 60) % 60;
247 let hour = (secs / 3600) % 24;
248 let days = secs / 86400;
249 let (year, month, day) = Log::days_to_ymd(days);
250 const MONTHS: [&str; 12] = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
251 format!("{:02}/{}/{:04}:{:02}:{:02}:{:02} +0000",
252 day, MONTHS[(month - 1) as usize], year, hour, min, sec)
253 }
254
255 fn days_to_ymd(days: u64) -> (u64, u64, u64) {
256 let z = days + 719468;
257 let era = z / 146097;
258 let doe = z % 146097;
259 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
260 let y = yoe + era * 400;
261 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
262 let mp = (5 * doy + 2) / 153;
263 let d = doy - (153 * mp + 2) / 5 + 1;
264 let m = if mp < 10 { mp + 3 } else { mp - 9 };
265 let y = if m <= 2 { y + 1 } else { y };
266 (y, m, d)
267 }
268
269 pub fn server_url_thread_count(protocol: &str, bind_addr: &String, thread_count: i32) -> String {
270 let url = format!("Server is up and running at: {}://{}\n", protocol, &bind_addr);
271 let thread_count = format!("Spawned {} thread(s) to handle incoming requests\n", thread_count);
272 [url, thread_count].join("")
273 }
274}