From bf97f1ed29d2c7f64aedc906f2c9b9e85e4d32a8 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Mon, 18 Jul 2022 18:50:11 -0400 Subject: [PATCH] Use tracing in our webassembly as well. --- Cargo.lock | 13 ++++ web/Cargo.toml | 2 + web/src/components/recipe_list.rs | 5 +- web/src/components/recipe_selection.rs | 11 +++- web/src/components/recipe_selector.rs | 7 +- web/src/components/shopping_list.rs | 7 +- web/src/lib.rs | 7 +- web/src/router_integration.rs | 42 +++++++----- web/src/service.rs | 37 ++++++----- web/src/typings.rs | 88 -------------------------- web/src/web.rs | 9 +-- 11 files changed, 89 insertions(+), 139 deletions(-) delete mode 100644 web/src/typings.rs diff --git a/Cargo.lock b/Cargo.lock index 80c6943..4305c33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -789,6 +789,8 @@ dependencies = [ "reqwasm", "serde_json", "sycamore", + "tracing", + "tracing-wasm", "wasm-bindgen", "web-sys", ] @@ -1624,6 +1626,17 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tracing-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +dependencies = [ + "tracing", + "tracing-subscriber", + "wasm-bindgen", +] + [[package]] name = "try-lock" version = "0.2.3" diff --git a/web/Cargo.toml b/web/Cargo.toml index 08a8f49..9559547 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -18,6 +18,8 @@ reqwasm = "0.5.0" # This makes debugging panics more tractable. console_error_panic_hook = "0.1.7" serde_json = "1.0.79" +tracing = "0.1.35" +tracing-wasm = "0.2.1" [dependencies.wasm-bindgen] # we need wasm-bindgen v0.2.78 exactly diff --git a/web/src/components/recipe_list.rs b/web/src/components/recipe_list.rs index aaaa3ba..b50624e 100644 --- a/web/src/components/recipe_list.rs +++ b/web/src/components/recipe_list.rs @@ -11,11 +11,12 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use crate::console_log; use crate::{components::Recipe, service::AppService}; use sycamore::{context::use_context, prelude::*}; +use tracing::{debug, instrument}; +#[instrument] #[component(RecipeList)] pub fn recipe_list() -> View { let app_service = use_context::(); @@ -26,7 +27,7 @@ pub fn recipe_list() -> View { Indexed(IndexedProps{ iterable: menu_list, template: |(idx, _count)| { - console_log!("Rendering recipe index: {}", idx); + debug!(idx=%idx, "Rendering recipe"); let idx = Signal::new(idx); view ! { Recipe(idx.handle()) diff --git a/web/src/components/recipe_selection.rs b/web/src/components/recipe_selection.rs index ba9feb5..da858e1 100644 --- a/web/src/components/recipe_selection.rs +++ b/web/src/components/recipe_selection.rs @@ -11,17 +11,22 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use crate::console_log; -use crate::service::AppService; use std::rc::Rc; use sycamore::{context::use_context, prelude::*}; +use tracing::{debug, instrument}; + +use crate::service::AppService; pub struct RecipeCheckBoxProps { pub i: usize, pub title: ReadSignal, } +#[instrument(skip(props), fields( + idx=%props.i, + title=%props.title.get() +))] #[component(RecipeSelection)] pub fn recipe_selection(props: RecipeCheckBoxProps) -> View { let app_service = use_context::(); @@ -36,7 +41,7 @@ pub fn recipe_selection(props: RecipeCheckBoxProps) -> View { label(for=id_cloned_2) { (props.title.get()) } input(type="number", class="item-count-sel", min="0", bind:value=count.clone(), name=format!("recipe_id:{}", i), value=id_as_str.clone(), on:change=move |_| { let mut app_service = app_service.clone(); - console_log!("setting recipe id: {} to count: {}", i, *count.get()); + debug!(idx=%i, count=*count.get(), "setting recipe count"); app_service.set_recipe_count_by_index(i, count.get().parse().unwrap()); }) } diff --git a/web/src/components/recipe_selector.rs b/web/src/components/recipe_selector.rs index c4eb3fb..fc0e470 100644 --- a/web/src/components/recipe_selector.rs +++ b/web/src/components/recipe_selector.rs @@ -11,11 +11,12 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use crate::console_error; use crate::{components::recipe_selection::*, service::AppService}; use sycamore::{context::use_context, futures::spawn_local_in_scope, prelude::*}; +use tracing::{error, instrument}; +#[instrument] #[component(RecipeSelector)] pub fn recipe_selector() -> View { let app_service = use_context::(); @@ -32,8 +33,8 @@ pub fn recipe_selector() -> View { spawn_local_in_scope(cloned!((app_service) => { let mut app_service = app_service.clone(); async move { - if let Err(e) = app_service.refresh().await { - console_error!("{}", e); + if let Err(err) = app_service.refresh().await { + error!(?err); }; } })); diff --git a/web/src/components/shopping_list.rs b/web/src/components/shopping_list.rs index 99b9827..6608881 100644 --- a/web/src/components/shopping_list.rs +++ b/web/src/components/shopping_list.rs @@ -14,9 +14,10 @@ use crate::service::AppService; use std::collections::{BTreeMap, BTreeSet}; -use crate::console_debug; use sycamore::{context::use_context, prelude::*}; +use tracing::{debug, instrument}; +#[instrument] #[component(ShoppingList)] pub fn shopping_list() -> View { let app_service = use_context::(); @@ -27,7 +28,7 @@ pub fn shopping_list() -> View { create_effect(cloned!((app_service, ingredients_map) => move || { ingredients_map.set(app_service.get_shopping_list()); })); - console_debug!("Ingredients map: {:?}", ingredients_map.get_untracked()); + debug!(ingredients_map=?ingredients_map.get_untracked()); let ingredients = create_memo(cloned!((ingredients_map, filtered_keys) => move || { let mut ingredients = Vec::new(); // This has the effect of sorting the ingredients by category @@ -40,7 +41,7 @@ pub fn shopping_list() -> View { } ingredients })); - console_debug!("Ingredients: {:?}", ingredients.get_untracked()); + debug!(ingredients = ?ingredients.get_untracked()); let table_view = Signal::new(View::empty()); create_effect( cloned!((table_view, ingredients, filtered_keys, modified_amts, extras) => move || { diff --git a/web/src/lib.rs b/web/src/lib.rs index 746c73e..d0cdc36 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -16,19 +16,20 @@ mod components; mod pages; mod router_integration; mod service; -mod typings; mod web; use sycamore::prelude::*; +#[cfg(feature = "web")] +use tracing_wasm; use wasm_bindgen::prelude::wasm_bindgen; use web::UI; #[wasm_bindgen(start)] pub fn main() { - #[cfg(debug_assertions)] - { + if cfg!(feature = "web") { console_error_panic_hook::set_once(); + tracing_wasm::set_as_global_default(); } sycamore::render(|| view! { UI() }); } diff --git a/web/src/router_integration.rs b/web/src/router_integration.rs index ab1c488..15bae45 100644 --- a/web/src/router_integration.rs +++ b/web/src/router_integration.rs @@ -16,16 +16,15 @@ use std::rc::Rc; use std::str::FromStr; use sycamore::prelude::*; +use tracing::{debug, error, instrument}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use web_sys::Event; use web_sys::{Element, HtmlAnchorElement}; use crate::app_state::AppRoutes; -use crate::console_debug; -use crate::console_error; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct BrowserIntegration(Signal<(String, String, String)>); impl BrowserIntegration { @@ -38,6 +37,7 @@ impl BrowserIntegration { ))) } + #[instrument(skip(self))] pub fn click_handler(&self) -> Box { let route_signal = self.0.clone(); Box::new(move |ev| { @@ -49,12 +49,12 @@ impl BrowserIntegration { .unwrap_throw() .map(|e| e.unchecked_into::()) { - console_debug!("handling navigation event."); + debug!("handling navigation event."); let location = web_sys::window().unwrap_throw().location(); if tgt.rel() == "external" { + debug!("External Link so ignoring."); return; - console_debug!("External Link so ignoring."); } let origin = tgt.origin(); @@ -67,12 +67,12 @@ impl BrowserIntegration { } (true, _, false) // different hash | (true, false, _) /* different path */ => { - console_debug!("different path or hash"); + debug!("different path or hash"); ev.prevent_default(); // Signal the pathname change let path = format!("{}{}{}", &origin, &tgt_pathname, &hash); - console_debug!("new route: ({}, {}, {})", origin, tgt_pathname, hash); - console_debug!("new path: ({})", &path); + debug!("new route: ({}, {}, {})", origin, tgt_pathname, hash); + debug!("new path: ({})", &path); route_signal.set((origin, tgt_pathname, hash)); // Update History API. let window = web_sys::window().unwrap_throw(); @@ -88,6 +88,7 @@ impl BrowserIntegration { } } +#[derive(Debug)] pub struct RouterProps where G: GenericNode, @@ -99,13 +100,18 @@ where pub browser_integration: BrowserIntegration, } +#[instrument(fields(?props.route, + origin=props.browser_integration.0.get().0, + pathn=props.browser_integration.0.get().1, + hash=props.browser_integration.0.get().2), + skip(props))] #[component(Router)] pub fn router(props: RouterProps) -> View where R: DeriveRoute + NotFound + Clone + Default + Debug + 'static, F: Fn(ReadSignal) -> View + 'static, { - console_debug!("Setting up router"); + debug!("Setting up router"); let integration = Rc::new(props.browser_integration); let route_select = Rc::new(props.route_select); @@ -113,10 +119,10 @@ where create_effect( cloned!((view_signal, integration, route_select) => move || { let path_signal = integration.0.clone(); - console_debug!("new path: {:?}", path_signal.get()); + debug!("new path: {:?}", path_signal.get()); let path = path_signal.clone(); let route = R::from(path.get().as_ref()); - console_debug!("new route: {:?}", &route); + debug!("new route: {:?}", &route); // TODO(jwall): this is an unnecessary use of signal. let view = route_select.as_ref()(Signal::new(route).handle()); register_click_handler(&view, integration.clone()); @@ -131,22 +137,23 @@ where } } +#[instrument(skip_all)] fn register_click_handler(view: &View, integration: Rc) where G: GenericNode, { - console_debug!("Registring click handler on node(s)"); + debug!("Registring click handler on node(s)"); if let Some(node) = view.as_node() { node.event("click", integration.click_handler()); } else if let Some(frag) = view.as_fragment() { - console_debug!("Fragment? {:?}", frag); + debug!(fragment=?frag); for n in frag { register_click_handler(n, integration.clone()); } } else if let Some(dyn_node) = view.as_dyn() { - console_debug!("Dynamic node? {:?}", dyn_node); + debug!(dynamic_node=?dyn_node); } else { - console_debug!("Unknown node? {:?}", view); + debug!(node=?view, "Unknown node"); } } @@ -165,8 +172,9 @@ pub trait DeriveRoute { } impl DeriveRoute for AppRoutes { + #[instrument] fn from(input: &(String, String, String)) -> AppRoutes { - console_debug!("routing: {input:?}"); + debug!("routing: {input:?}"); match input.2.as_str() { "" => AppRoutes::default(), "#plan" => AppRoutes::Plan, @@ -183,7 +191,7 @@ impl DeriveRoute for AppRoutes { }; } } - console_error!("Path not found: [{:?}]", input); + error!("Path not found: [{:?}]", input); AppRoutes::NotFound } } diff --git a/web/src/service.rs b/web/src/service.rs index 66918c1..b867f24 100644 --- a/web/src/service.rs +++ b/web/src/service.rs @@ -13,10 +13,9 @@ // limitations under the License. use std::collections::{BTreeMap, BTreeSet}; -use crate::{console_debug, console_error, console_log}; - use reqwasm::http; use sycamore::prelude::*; +use tracing::{debug, error, info, instrument}; use web_sys::{window, Storage}; use recipes::{parse, Ingredient, IngredientAccumulator, Recipe}; @@ -46,6 +45,7 @@ impl AppService { .map_err(|e| format!("{:?}", e)) } + #[instrument] async fn fetch_recipes_http() -> Result { let resp = match http::Request::get("/api/v1/recipes").send().await { Ok(resp) => resp, @@ -54,52 +54,55 @@ impl AppService { if resp.status() != 200 { return Err(format!("Status: {}", resp.status())); } else { - console_debug!("We got a valid response back!"); + debug!("We got a valid response back!"); return Ok(resp.text().await.map_err(|e| format!("{}", e))?); } } + #[instrument] async fn fetch_categories_http() -> Result, String> { let resp = match http::Request::get("/api/v1/categories").send().await { Ok(resp) => resp, Err(e) => return Err(format!("Error: {}", e)), }; if resp.status() == 404 { - console_debug!("Categories returned 404"); + debug!("Categories returned 404"); return Ok(None); } else if resp.status() != 200 { return Err(format!("Status: {}", resp.status())); } else { - console_debug!("We got a valid response back!"); + debug!("We got a valid response back!"); return Ok(Some(resp.text().await.map_err(|e| format!("{}", e))?)); } } + #[instrument] async fn synchronize() -> Result<(), String> { - console_log!("Synchronizing Recipes"); + info!("Synchronizing Recipes"); let storage = Self::get_storage()?.unwrap(); let recipes = Self::fetch_recipes_http().await?; storage .set_item("recipes", &recipes) .map_err(|e| format!("{:?}", e))?; - console_log!("Synchronizing categories"); + info!("Synchronizing categories"); match Self::fetch_categories_http().await { Ok(Some(categories_content)) => { - console_debug!("categories: {}", categories_content); + debug!(categories=?categories_content); storage .set_item("categories", &categories_content) .map_err(|e| format!("{:?}", e))?; } Ok(None) => { - console_error!("There is no category file"); + error!("There is no category file"); } Err(e) => { - console_error!("{}", e); + error!("{}", e); } } Ok(()) } + #[instrument] pub fn fetch_categories_from_storage() -> Result>, String> { let storage = Self::get_storage()?.unwrap(); match storage @@ -111,7 +114,7 @@ impl AppService { match parse::as_categories(&parsed) { Ok(categories) => Ok(Some(categories)), Err(e) => { - console_debug!("Error parsing categories {}", e); + debug!("Error parsing categories {}", e); Err(format!("Error parsing categories {}", e)) } } @@ -120,6 +123,7 @@ impl AppService { } } + #[instrument] pub fn fetch_recipes_from_storage( ) -> Result<(Option, Option>), String> { let storage = Self::get_storage()?.unwrap(); @@ -136,11 +140,10 @@ impl AppService { let recipe = match parse::as_recipe(&r) { Ok(r) => r, Err(e) => { - console_error!("Error parsing recipe {}", e); + error!("Error parsing recipe {}", e); continue; } }; - //console_debug!("We parsed a recipe {}", recipe.title); if recipe.title == "Staples" { staples = Some(recipe); } else { @@ -161,14 +164,15 @@ impl AppService { Ok(Self::fetch_categories_from_storage()?) } + #[instrument(skip(self))] pub async fn refresh(&mut self) -> Result<(), String> { Self::synchronize().await?; - console_debug!("refreshing recipes"); + debug!("refreshing recipes"); if let (staples, Some(r)) = Self::fetch_recipes().await? { self.set_recipes(r); self.staples.set(staples); } - console_debug!("refreshing categories"); + debug!("refreshing categories"); if let Some(categories) = Self::fetch_categories().await? { self.set_categories(categories); } @@ -179,6 +183,7 @@ impl AppService { self.recipes.get().get(idx).map(|(_, r)| r.clone()) } + #[instrument(skip(self))] pub fn get_shopping_list(&self) -> BTreeMap)>> { let mut acc = IngredientAccumulator::new(); let recipe_counts = self.menu_list.get(); @@ -205,7 +210,7 @@ impl AppService { .or_insert(vec![]) .push((i.clone(), recipes.clone())); } - console_debug!("Category map {:?}", self.category_map); + debug!(?self.category_map); // FIXM(jwall): Sort by categories and names. groups } diff --git a/web/src/typings.rs b/web/src/typings.rs deleted file mode 100644 index 6f419f4..0000000 --- a/web/src/typings.rs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2022 Jeremy Wall -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -extern "C" { - // Use `js_namespace` here to bind `console.log(..)` instead of just - // `log(..)` - #[wasm_bindgen(js_namespace = console)] - pub fn log(s: &str); - #[wasm_bindgen(js_namespace = console)] - pub fn debug(s: &str); - #[wasm_bindgen(js_namespace = console)] - pub fn warn(s: &str); - #[wasm_bindgen(js_namespace = console)] - pub fn error(s: &str); -} - -#[macro_export] -macro_rules! console_log { - // Note that this is using the `log` function imported above during - // `bare_bones` - ($($t:tt)*) => { - if cfg!(feature="web") { - use crate::typings::log; - log(&format_args!($($t)*).to_string()); - } else if cfg!(feature="ssr") { - println!($($t)*); - } - } -} - -#[macro_export] -macro_rules! console_debug { - // Note that this is using the `log` function imported above during - // `bare_bones` - ($($t:tt)*) => {{ - if cfg!(feature="web") { - use crate::typings::debug; - debug(&format_args!($($t)*).to_string()); - } else if cfg!(feature="ssr") { - print!("DEBUG: "); - println!($($t)*); - } - }} -} - -#[macro_export] -macro_rules! console_error { - // Note that this is using the `error` function imported above during - // `bare_bones` - ($($t:tt)*) => {{ - if cfg!(feature="web") - { - use crate::typings::error; - error(&format_args!($($t)*).to_string()); - } else if cfg!(feature="ssr") { - print!("ERROR: "); - println!($($t)*); - }; - }} -} - -#[macro_export] -macro_rules! console_warn { - // Note that this is using the `warn` function imported above during - // `bare_bones` - ($($t:tt)*) => {{ - if cfg!("web") { - use crate::typings::warn; - (warn(&format_args!($($t)*).to_string())) - } else if cfg!(feature="ssr") { - print!("WARN: "); - (println!($($t)*)) - } - }} -} diff --git a/web/src/web.rs b/web/src/web.rs index 75d0733..6cacb6a 100644 --- a/web/src/web.rs +++ b/web/src/web.rs @@ -13,7 +13,7 @@ // limitations under the License. use crate::pages::*; use crate::{app_state::*, components::*, router_integration::*, service::AppService}; -use crate::{console_error, console_log}; +use tracing::{error, info, instrument}; use sycamore::{ context::{ContextProvider, ContextProviderProps}, @@ -51,10 +51,11 @@ fn route_switch(route: ReadSignal) -> View { }) } +#[instrument] #[component(UI)] pub fn ui() -> View { let app_service = AppService::new(); - console_log!("Starting UI"); + info!("Starting UI"); view! { // NOTE(jwall): Set the app_service in our toplevel scope. Children will be able // to find the service as long as they are a child of this scope. @@ -70,9 +71,9 @@ pub fn ui() -> View { app_service.set_recipes(recipes); } Ok((_, None)) => { - console_error!("No recipes to find"); + error!("No recipes to find"); } - Err(msg) => console_error!("Failed to get recipes {}", msg), + Err(msg) => error!("Failed to get recipes {}", msg), } } });