What the Worker writes to its log, and the rule that it never writes a visitor.
A line is built only from the closed types below: the route (one of a fixed set, never the
path the visitor asked for), the method, the status and how long it took. There is no way
to hand line an address, a header, a query or a body, so none can be logged by accident.
(The Worker never reads the client's address at all.) Cloudflare keeps its own request
records for the account as any host does; this file is only about what the code writes.
Levels: error for a response the site got wrong (5xx), warn for one the visitor got
wrong or a file that should exist and does not (4xx), info for a page served, debug
for the small things a page asks for.
13use std::fmt;
One of the shared fonts (jev_ui::asset).
The link preview's video, which this Worker answers itself (media.rs).
29 Video,
The link preview's picture, which this Worker passes to the assets (image in lib.rs).
31 Image,
A file under /preview/ that the assets did not serve: it was never generated.
37impl Route {
Which route a path is. The path itself goes no further than this function.
39 pub fn of(path: &str) -> Route { 40 match path { 41 "/" => Route::Page, 42 "/datastar.js" | "/ui.js" => Route::Script, 43 "/cat.css" | "/ui.css" => Route::Style, 44 p if p.starts_with("/fonts/") => Route::Font, 45 "/favicon.svg" => Route::Icon, 46 "/mood" => Route::Mood, 47 "/mascot" => Route::Mascot, 48 "/tick" => Route::Tick, 49 "/tour" => Route::Tour, 50 "/robots.txt" => Route::Robots, 51 crate::media::PATH => Route::Video, 52 "/preview/card.png" => Route::Image, 53 p if p.starts_with("/preview/") => Route::PreviewMissing, 54 _ => Route::Other, 55 } 56 }
58 fn name(self) -> &'static str { 59 match self { 60 Route::Page => "page", 61 Route::Script => "script", 62 Route::Style => "style", 63 Route::Font => "font", 64 Route::Icon => "icon", 65 Route::Mood => "mood", 66 Route::Mascot => "mascot", 67 Route::Tick => "tick", 68 Route::Tour => "tour", 69 Route::Robots => "robots", 70 Route::Video => "video", 71 Route::Image => "image", 72 Route::PreviewMissing => "preview-missing", 73 Route::Other => "other", 74 } 75 } 76} 77 78#[derive(Clone, Copy, Debug, PartialEq, Eq)] 79pub enum Level { 80 Debug, 81 Info, 82 Warn, 83 Error, 84} 85 86pub struct Served { 87 pub route: Route, 88 pub method: &'static str, 89 pub status: u16, 90 pub millis: u64, 91} 92 93impl Served { 94 pub fn level(&self) -> Level { 95 match (self.status, self.route) { 96 (500.., _) => Level::Error, 97 (_, Route::PreviewMissing) => Level::Warn, 98 (400..=499, Route::Other | Route::Font) | (416, Route::Video) => Level::Debug, 99 (400..=499, _) => Level::Warn, 100 (_, Route::Page) => Level::Info, 101 _ => Level::Debug, 102 } 103 } 104} 105 106impl fmt::Display for Served { 107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 108 write!(f, "{} {} -> {} in {} ms", self.method, self.route.name(), self.status, self.millis)?; 109 if self.route == Route::PreviewMissing { 110 write!(f, " (run web/tools/preview: the link preview's files were not built)")?; 111 } 112 Ok(()) 113 } 114}
The method as a fixed word, so a made-up one cannot reach the log.
126pub fn write(served: &Served) { 127 emit(served.level(), &served.to_string()); 128} 129 130#[cfg(target_arch = "wasm32")] 131fn emit(level: Level, line: &str) { 132 match level { 133 Level::Debug => worker::console_debug!("{line}"), 134 Level::Info => worker::console_log!("{line}"), 135 Level::Warn => worker::console_warn!("{line}"), 136 Level::Error => worker::console_error!("{line}"), 137 } 138}
Native builds (the tests) have no console to write to.
Something the Worker itself got wrong, in words that carry no request.
150#[cfg(not(target_arch = "wasm32"))] 151pub fn failure(_what: &str) {} 152 153#[cfg(test)] 154mod tests { 155 use super::*; 156 157 fn served(route: Route, status: u16) -> Served { 158 Served { route, method: "GET", status, millis: 3 } 159 } 160 161 #[test] 162 fn a_path_is_reduced_to_a_route_and_goes_no_further() { 163 assert_eq!(Route::of("/"), Route::Page); 164 assert_eq!(Route::of("/mood"), Route::Mood); 165 assert_eq!(Route::of("/mascot"), Route::Mascot); 166 assert_eq!(Route::of("/preview/loop.mp4"), Route::Video); 167 assert_eq!(Route::of("/preview/card.png"), Route::Image); 168 assert_eq!(Route::of("/preview/other.png"), Route::PreviewMissing); 169 assert_eq!(Route::of("/wp-login.php?x=1.2.3.4"), Route::Other); 170 let line = served(Route::of("/some/secret/path"), 404).to_string(); 171 assert!(!line.contains("secret"), "{line}"); 172 } 173 174 #[test] 175 fn levels_follow_who_was_wrong() { 176 assert_eq!(served(Route::Page, 200).level(), Level::Info); 177 assert_eq!(served(Route::Style, 200).level(), Level::Debug); 178 assert_eq!(served(Route::Mood, 422).level(), Level::Warn); 179 assert_eq!(served(Route::Other, 404).level(), Level::Debug); 180 assert_eq!(served(Route::PreviewMissing, 404).level(), Level::Warn); 181 assert_eq!(served(Route::Image, 200).level(), Level::Debug); 182 assert_eq!(served(Route::Image, 404).level(), Level::Warn); 183 assert_eq!(served(Route::Video, 206).level(), Level::Debug); 184 assert_eq!(served(Route::Video, 416).level(), Level::Debug); 185 assert_eq!(served(Route::Video, 404).level(), Level::Warn); 186 assert_eq!(served(Route::Page, 500).level(), Level::Error); 187 } 188 189 #[test] 190 fn a_line_says_what_happened_and_nothing_about_who_asked() { 191 assert_eq!(served(Route::Tick, 200).to_string(), "GET tick -> 200 in 3 ms"); 192 assert_eq!(method(&http::Method::from_bytes(b"BREW").unwrap()), "OTHER"); 193 } 194}