diff --git a/backend/src/api/log_it.rs b/backend/src/api/log_it.rs new file mode 100644 index 0000000..5b06cbf --- /dev/null +++ b/backend/src/api/log_it.rs @@ -0,0 +1,35 @@ +use usdpl_back::core::serdes::Primitive; + +use super::ApiParameterType; + +/// API web method to send log messages to the back-end log, callable from the front-end +pub fn log_it() -> impl Fn(ApiParameterType) -> ApiParameterType { + move |params| { + if let Some(Primitive::F64(level)) = params.get(0) { + if let Some(Primitive::String(msg)) = params.get(1) { + log_msg_by_level(*level as u8, msg); + vec![true.into()] + } else if let Some(Primitive::Json(msg)) = params.get(1) { + log_msg_by_level(*level as u8, msg); + vec![true.into()] + } else { + log::warn!("Got log_it call with wrong/missing 2nd parameter"); + vec![false.into()] + } + } else { + log::warn!("Got log_it call with wrong/missing 1st parameter"); + vec![false.into()] + } + } +} + +fn log_msg_by_level(level: u8, msg: &str) { + match level { + 1 => log::trace!("FRONT-END: {}", msg), + 2 => log::debug!("FRONT-END: {}", msg), + 3 => log::info!("FRONT-END: {}", msg), + 4 => log::warn!("FRONT-END: {}", msg), + 5 => log::error!("FRONT-END: {}", msg), + _ => log::trace!("FRONT-END: {}", msg), + } +} diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index 8ad0f7b..87f0edd 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -2,6 +2,7 @@ mod about; pub(crate) mod async_utils; mod get_display; mod get_items; +mod log_it; mod on_event; mod on_javascript_result; mod on_update; @@ -13,6 +14,7 @@ mod types; pub use about::get_about; pub use get_display::GetDisplayEndpoint; pub use get_items::get_items; +pub use log_it::log_it; pub use on_event::on_event; pub use on_javascript_result::on_javascript_result; pub use on_update::on_update; diff --git a/backend/src/api/on_event.rs b/backend/src/api/on_event.rs index 851c84d..f3e18ae 100644 --- a/backend/src/api/on_event.rs +++ b/backend/src/api/on_event.rs @@ -25,7 +25,7 @@ pub fn on_event(sender: Sender) -> impl Fn(ApiParameterType) -> ApiPa vec![true.into()] }, Err(e) => { - log::warn!("Failed to parse event json: {}", e); + log::error!("Failed to parse event json: {}", e); vec![false.into()] } } diff --git a/backend/src/main.rs b/backend/src/main.rs index a9cb1da..84f97d5 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -31,6 +31,7 @@ fn main() -> Result<(), ()> { .register_async("get_display", api::GetDisplayEndpoint::new(sender.clone())) .register_async("get_javascript_to_run", api::GetJavascriptEndpoint::new(sender.clone())) .register_blocking("get_items", api::get_items(sender.clone())) + .register("log", api::log_it()) .register("on_javascript_result", api::on_javascript_result(sender.clone())) .register("on_update", api::on_update(sender.clone())) .register("on_steam_event", api::on_event(sender.clone())) diff --git a/main.py b/main.py index 5b441fd..4feca11 100644 --- a/main.py +++ b/main.py @@ -11,6 +11,6 @@ class Plugin: # Asyncio-compatible long-running code, executed in a task when the plugin is loaded async def _main(self): # startup - #self.backend_proc = subprocess.Popen([PARENT_DIR + "/bin/backend", "--config", ""]) + self.backend_proc = subprocess.Popen([PARENT_DIR + "/bin/backend", "--config", ""]) while True: await asyncio.sleep(1) diff --git a/src/backend.ts b/src/backend.ts index dc28a4d..332968f 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -98,6 +98,14 @@ export type CJavascriptResult = { export type CJavascriptResponse = CJavascriptResult | CErrorResult; +export enum CLogLevel { + TRACE = 1, + DEBUG = 2, + INFO = 3, + WARN = 4, + ERROR = 5, +} + export async function getElements(): Promise { return (await call_backend("get_items", []))[0]; } @@ -129,3 +137,7 @@ export async function onJavascriptResult(id: number, value: any): Promise { return (await call_backend("on_steam_event", [data]))[0]; } + +export async function log(level: CLogLevel, msg: string): Promise { + return (await call_backend("log", [level, msg]))[0]; +} diff --git a/src/index.tsx b/src/index.tsx index 6887e0e..b5d985b 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -35,36 +35,58 @@ let update = () => {}; let updateTasks: (() => void)[] = []; +let displayErrors: number[] = []; +const DISPLAY_ERROR_ABORT_THRESHHOLD = 8; + function displayCallback(index: number) { return (newVal: backend.CDisplayResponse) => { if (newVal != null) { switch (newVal.result) { case "value": + displayErrors[index] = 0; let val = newVal as backend.CValueResult; console.log("CAYLON: Got display for " + index.toString(), val); + backend.log(backend.CLogLevel.DEBUG, "Got display for " + index.toString()); set_value(DISPLAY_KEY + index.toString(), val.value); break; case "error": + displayErrors[index]++; let err = newVal as backend.CErrorResult; console.warn("CAYLON: Got display error for " + index.toString(), err); + backend.log(backend.CLogLevel.WARN, "Got display error for " + index.toString()); break; default: console.error("CAYLON: Got invalid display response for " + index.toString(), newVal); + backend.log(backend.CLogLevel.ERROR, "Got invalid display response for " + index.toString()); break; } } else { + displayErrors[index]++; console.warn("CAYLON: Ignoring null display result for " + index.toString()); + backend.log(backend.CLogLevel.WARN, "Ignoring null display result for " + index.toString()); + } + if (displayErrors[index] < DISPLAY_ERROR_ABORT_THRESHHOLD) { + updateTasks.push(() => backend.resolve(backend.getDisplay(index), displayCallback(index))); + update(); + } else { + console.error("CAYLON: Got too many display errors for " + index.toString() + ", stopping display updates for element"); + backend.log(backend.CLogLevel.ERROR, "Got too many display errors for " + index.toString() + ", stopping display updates for element"); } - updateTasks.push(() => backend.resolve(backend.getDisplay(index), displayCallback(index))); - update(); } } +let jsErrors: number = 0; +const JAVASCRIPT_ERROR_ABORT_THRESHHOLD = 16; + function onGetElements() { + displayErrors = []; for (let i = 0; i < items.length; i++) { console.log("CAYLON: req display for item #" + i.toString()); + backend.log(backend.CLogLevel.DEBUG, "req display for item #" + i.toString()); + displayErrors.push(0); backend.resolve(backend.getDisplay(i), displayCallback(i)); } + jsErrors = 0; backend.resolve(backend.getJavascriptToRun(), jsCallback()); register_for_steam_events(); } @@ -73,24 +95,43 @@ const eval2 = eval; function jsCallback() { return (script: backend.CJavascriptResponse) => { + // register next callback (before running JS, in case that crashes) + if (jsErrors < JAVASCRIPT_ERROR_ABORT_THRESHHOLD) { + backend.resolve(backend.getJavascriptToRun(), jsCallback()); + } else { + console.error("CAYLON: Got too many javascript errors, stopping remote javascript execution"); + backend.log(backend.CLogLevel.ERROR, "Got too many javascript errors, stopping remote javascript execution"); + } if (script != null) { switch (script.result) { case "javascript": - // register next callback (before running JS, in case that crashes) - backend.resolve(backend.getJavascriptToRun(), jsCallback()); let toRun = script as backend.CJavascriptResult; console.log("CAYLON: Got javascript " + toRun.id.toString(), toRun); - let result = eval2(toRun.raw); - backend.onJavascriptResult(toRun.id, result); + backend.log(backend.CLogLevel.DEBUG, "Got javascript " + toRun.id.toString()); + try { + let result = eval2(toRun.raw); + backend.onJavascriptResult(toRun.id, result); + jsErrors = 0; + } catch (err) { + jsErrors++; + console.warn("CAYLON: Javascript " + toRun.id.toString() + "failed", err); + backend.log(backend.CLogLevel.WARN, "Javascript " + toRun.id.toString() + "failed"); + } break; case "error": + jsErrors++; let err = script as backend.CErrorResult; console.warn("CAYLON: Got javascript retrieval error", err); + backend.log(backend.CLogLevel.WARN, "Got javascript retrieval error"); break; default: + jsErrors++; console.error("CAYLON: Got invalid javascript response", script); + backend.log(backend.CLogLevel.ERROR, "Got invalid javascript response"); break; } + } else { + jsErrors++; } } } @@ -102,14 +143,17 @@ function jsCallback() { let about_promise = backend.getAbout(); let elements_promise = backend.getElements(); about = await about_promise; - console.log("CAYLON: got about", about); + console.log("CAYLON: Got about", about); + backend.log(backend.CLogLevel.DEBUG, "Got about"); let result = await elements_promise; - console.log("CAYLON: got elements", result); + console.log("CAYLON: Got elements", result); + backend.log(backend.CLogLevel.DEBUG, "Got elements"); if (result != null) { items = result; onGetElements(); } else { - console.warn("CAYLON: backend connection failed"); + console.error("CAYLON: Backend connection failed"); + backend.log(backend.CLogLevel.ERROR, "Backend connection failed"); } backend.resolve(backend.getJavascriptToRun(), jsCallback()); register_for_steam_events(); @@ -149,19 +193,22 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { backend.resolve(backend.reload(), (reload_items: backend.CElement[]) => { items = reload_items; - console.log("CAYLON: got elements", reload_items); + console.log("CAYLON: Got elements", reload_items); + backend.log(backend.CLogLevel.DEBUG, "Got elements for reload"); if (reload_items != null) { items = reload_items; onGetElements(); } else { - console.warn("CAYLON: backend connection failed"); + console.error("CAYLON: Backend connection failed"); + backend.log(backend.CLogLevel.ERROR, "Backend connection failed on reload"); } update(); }); backend.resolve(backend.getAbout(), (new_about: backend.CAbout) => { about = new_about; - console.log("CAYLON: got about", about); + console.log("CAYLON: Got about", about); + backend.log(backend.CLogLevel.DEBUG, "Got about for reload"); update(); }); }}> @@ -188,6 +235,7 @@ function buildHtmlElement(element: backend.CElement, index: number, updateIdc: a return buildEventDisplay(element as backend.CEventDisplay, index, updateIdc); } console.error("CAYLON: Unsupported element", element); + backend.log(backend.CLogLevel.ERROR, "Unsupported element " + element.element); return
Unsupported
; }