Use tracing in our webassembly as well.

This commit is contained in:
Jeremy Wall 2022-07-18 18:50:11 -04:00
parent cf38056104
commit bf97f1ed29
11 changed files with 89 additions and 139 deletions

13
Cargo.lock generated
View File

@ -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"

View File

@ -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

View File

@ -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<G>)]
pub fn recipe_list() -> View<G> {
let app_service = use_context::<AppService>();
@ -26,7 +27,7 @@ pub fn recipe_list() -> View<G> {
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())

View File

@ -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<String>,
}
#[instrument(skip(props), fields(
idx=%props.i,
title=%props.title.get()
))]
#[component(RecipeSelection<G>)]
pub fn recipe_selection(props: RecipeCheckBoxProps) -> View<G> {
let app_service = use_context::<AppService>();
@ -36,7 +41,7 @@ pub fn recipe_selection(props: RecipeCheckBoxProps) -> View<G> {
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());
})
}

View File

@ -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<G>)]
pub fn recipe_selector() -> View<G> {
let app_service = use_context::<AppService>();
@ -32,8 +33,8 @@ pub fn recipe_selector() -> View<G> {
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);
};
}
}));

View File

@ -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<G>)]
pub fn shopping_list() -> View<G> {
let app_service = use_context::<AppService>();
@ -27,7 +28,7 @@ pub fn shopping_list() -> View<G> {
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<G> {
}
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 || {

View File

@ -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() });
}

View File

@ -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<dyn Fn(web_sys::Event)> {
let route_signal = self.0.clone();
Box::new(move |ev| {
@ -49,12 +49,12 @@ impl BrowserIntegration {
.unwrap_throw()
.map(|e| e.unchecked_into::<HtmlAnchorElement>())
{
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<R, F, G>
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<G>)]
pub fn router<R, F>(props: RouterProps<R, F, G>) -> View<G>
where
R: DeriveRoute + NotFound + Clone + Default + Debug + 'static,
F: Fn(ReadSignal<R>) -> View<G> + '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<G>(view: &View<G>, integration: Rc<BrowserIntegration>)
where
G: GenericNode<EventType = Event>,
{
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
}
}

View File

@ -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<String, String> {
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<Option<String>, 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<Option<BTreeMap<String, String>>, 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<Recipe>, Option<Vec<(usize, Recipe)>>), 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<String, Vec<(Ingredient, BTreeSet<String>)>> {
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
}

View File

@ -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)*))
}
}}
}

View File

@ -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<G: Html>(route: ReadSignal<AppRoutes>) -> View<G> {
})
}
#[instrument]
#[component(UI<G>)]
pub fn ui() -> View<G> {
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<G> {
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),
}
}
});