Add option to turn on logging to IndexedDB + delete logs + download
This commit is contained in:
parent
a8c279e13e
commit
7fddf30657
142
Cargo.lock
generated
142
Cargo.lock
generated
@ -2,6 +2,18 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "accessory"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87537f9ae7cfa78d5b8ebd1a1db25959f5e737126be4d8eb44a5452fc4b63cde"
|
||||
dependencies = [
|
||||
"macroific",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.73",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.22.0"
|
||||
@ -148,6 +160,18 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "delegate-display"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98a85201f233142ac819bbf6226e36d0b5e129a47bd325084674261c82d4cd66"
|
||||
dependencies = [
|
||||
"macroific",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.73",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
@ -160,6 +184,18 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "fancy_constructor"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07b19d0e43eae2bfbafe4931b5e79c73fb1a849ca15cd41a761a7b8587f9a1a2"
|
||||
dependencies = [
|
||||
"macroific",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.73",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@ -701,6 +737,23 @@ dependencies = [
|
||||
"syn 2.0.73",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexed_db_futures"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43315957678a70eb21fb0d2384fe86dde0d6c859a01e24ce127eb65a0143d28c"
|
||||
dependencies = [
|
||||
"accessory",
|
||||
"cfg-if",
|
||||
"delegate-display",
|
||||
"fancy_constructor",
|
||||
"js-sys",
|
||||
"uuid",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.3.0"
|
||||
@ -728,9 +781,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.69"
|
||||
version = "0.3.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
|
||||
checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@ -747,6 +800,53 @@ version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "macroific"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f05c00ac596022625d01047c421a0d97d7f09a18e429187b341c201cb631b9dd"
|
||||
dependencies = [
|
||||
"macroific_attr_parse",
|
||||
"macroific_core",
|
||||
"macroific_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macroific_attr_parse"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd94d5da95b30ae6e10621ad02340909346ad91661f3f8c0f2b62345e46a2f67"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.73",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macroific_core"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13198c120864097a565ccb3ff947672d969932b7975ebd4085732c9f09435e55"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.73",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macroific_macro"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c9853143cbed7f1e41dc39fee95f9b361bec65c8dc2a01bf609be01b61f5ae"
|
||||
dependencies = [
|
||||
"macroific_attr_parse",
|
||||
"macroific_core",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.73",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
@ -1299,6 +1399,16 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
@ -1333,19 +1443,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.92"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
||||
checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.92"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
||||
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
@ -1358,9 +1469,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.42"
|
||||
version = "0.4.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
|
||||
checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@ -1370,9 +1481,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.92"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
||||
checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@ -1380,9 +1491,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.92"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1393,9 +1504,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.92"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
@ -1424,18 +1535,21 @@ dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"gc-arena",
|
||||
"im",
|
||||
"indexed_db_futures",
|
||||
"itertools",
|
||||
"minicrossterm",
|
||||
"nom",
|
||||
"piccolo",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde-wasm-bindgen 0.6.5",
|
||||
"serde_json",
|
||||
"strip-ansi-escapes",
|
||||
"thiserror",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"yew",
|
||||
]
|
||||
|
@ -13,7 +13,7 @@ piccolo = { git = "https://github.com/kyren/piccolo.git", rev = "fcbaabc92429217
|
||||
unicode-segmentation = "1.11.0"
|
||||
unicode-width = "0.1.13"
|
||||
wasm-bindgen = "0.2.92"
|
||||
web-sys = { version = "0.3.69", features = ["ResizeObserver", "DomRect", "CssStyleDeclaration"] }
|
||||
web-sys = { version = "0.3.69", features = ["ResizeObserver", "DomRect", "CssStyleDeclaration", "HtmlAnchorElement"] }
|
||||
yew = { version = "0.21.0", features = ["csr"] }
|
||||
minicrossterm = { git = "https://git.blastmud.org/blasthavers/minicrossterm.git", rev = "0c8c6d4f0cf445adf7bb957811081a1b710bd933" }
|
||||
thiserror = "1.0.63"
|
||||
@ -24,3 +24,6 @@ serde_json = "1.0.127"
|
||||
gc-arena = { git = "https://github.com/kyren/gc-arena.git", rev = "5a7534b883b703f23cfb8c3cfdf033460aa77ea9" }
|
||||
regex = "1.10.6"
|
||||
strip-ansi-escapes = "0.2.0"
|
||||
indexed_db_futures = "0.5.0"
|
||||
wasm-bindgen-futures = "0.4.43"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
|
544
src/logging.rs
Normal file
544
src/logging.rs
Normal file
@ -0,0 +1,544 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
mem::replace,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use indexed_db_futures::{
|
||||
idb_object_store::IdbObjectStoreParameters,
|
||||
request::{IdbOpenDbRequestLike, OpenDbRequest},
|
||||
IdbDatabase, IdbKeyPath, IdbQuerySource, IdbVersionChangeEvent,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use wasm_bindgen_futures::{
|
||||
js_sys::{Array, Uint8Array},
|
||||
spawn_local,
|
||||
};
|
||||
use web_sys::{
|
||||
console, js_sys::Date, Blob, BlobPropertyBag, DomException, HtmlAnchorElement, IdbKeyRange,
|
||||
IdbTransactionMode, Url,
|
||||
};
|
||||
|
||||
use crate::{echo_to_term_frame, GlobalMemoCell, TermFrame};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum QueuedAction {
|
||||
LogEvent(LogEvent),
|
||||
ReportOnLogStreams(TermFrame),
|
||||
DeleteLogs(DeleteLogs),
|
||||
DownloadLogs(DownloadLogs),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DeleteLogs {
|
||||
pub reply_to: TermFrame,
|
||||
pub stream: String,
|
||||
pub min_date: String,
|
||||
pub max_date: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DownloadLogs {
|
||||
pub reply_to: TermFrame,
|
||||
pub stream: String,
|
||||
pub min_date: String,
|
||||
pub max_date: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LogEvent {
|
||||
pub log_stream: String,
|
||||
pub timestamp: Date,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct StoredLogEvent {
|
||||
pub stream: String,
|
||||
pub timestamp: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl From<&LogEvent> for StoredLogEvent {
|
||||
fn from(value: &LogEvent) -> Self {
|
||||
Self {
|
||||
stream: value.log_stream.clone(),
|
||||
timestamp: value
|
||||
.timestamp
|
||||
.to_iso_string()
|
||||
.as_string()
|
||||
.expect("timestamp to_iso_string wasn't string"),
|
||||
message: value.message.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum LoggingEngine {
|
||||
Uninitialised,
|
||||
Initialising { backlog: Vec<QueuedAction> },
|
||||
Unavailable,
|
||||
Ready { db: Arc<IdbDatabase> },
|
||||
}
|
||||
|
||||
impl Default for LoggingEngine {
|
||||
fn default() -> Self {
|
||||
Self::Uninitialised
|
||||
}
|
||||
}
|
||||
|
||||
async fn async_init_logging() -> Result<LoggingEngine, DomException> {
|
||||
let mut db_req: OpenDbRequest = IdbDatabase::open_u32("logging", 1)?;
|
||||
db_req.set_on_upgrade_needed(Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
|
||||
if evt
|
||||
.db()
|
||||
.object_store_names()
|
||||
.find(|n| n == "logs")
|
||||
.is_none()
|
||||
{
|
||||
let store = evt.db().create_object_store_with_params(
|
||||
"logs",
|
||||
<IdbObjectStoreParameters as Default>::default().auto_increment(true),
|
||||
)?;
|
||||
store.create_index(
|
||||
"by_stream_timestamp",
|
||||
&IdbKeyPath::str_sequence(&["stream", "timestamp"]),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
let db: IdbDatabase = db_req.await?;
|
||||
|
||||
Ok(LoggingEngine::Ready { db: db.into() })
|
||||
}
|
||||
|
||||
fn logging_broken_message(global: &GlobalMemoCell, frame: &TermFrame) {
|
||||
let _ = echo_to_term_frame(global, frame, "Couldn't enable logging. This sometimes happens if your browser doesn't enable indexed storage, or is in a mode (e.g. private browsing) where such storage is not permitted.\r\n");
|
||||
}
|
||||
|
||||
fn init_logging(global: &GlobalMemoCell) {
|
||||
let global: GlobalMemoCell = global.clone();
|
||||
spawn_local(async move {
|
||||
match async_init_logging().await {
|
||||
Ok(new_engine) => {
|
||||
let old_engine = replace(global.log_engine.borrow_mut().deref_mut(), new_engine);
|
||||
if let LoggingEngine::Initialising { backlog } = old_engine {
|
||||
for action in backlog.into_iter() {
|
||||
match action {
|
||||
QueuedAction::LogEvent(ev) => queue_immediate_log(&global, ev),
|
||||
QueuedAction::ReportOnLogStreams(frame) => {
|
||||
immediate_report_log_frame(&global, frame);
|
||||
}
|
||||
QueuedAction::DeleteLogs(act) => {
|
||||
immediate_delete_logs(&global, act);
|
||||
}
|
||||
QueuedAction::DownloadLogs(act) => {
|
||||
immediate_download_logs(&global, act);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
if let LoggingEngine::Initialising { .. } = global.log_engine.borrow().deref() {
|
||||
logging_broken_message(&global, &TermFrame(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn queue_immediate_log_refutable(db: &IdbDatabase, event: &LogEvent) -> Result<(), String> {
|
||||
let trans = db
|
||||
.transaction_on_one_with_mode("logs", IdbTransactionMode::Readwrite)
|
||||
.map_err(|e| e.message())?;
|
||||
let store = trans.object_store("logs").map_err(|e| e.message())?;
|
||||
store
|
||||
.put_val(
|
||||
&serde_wasm_bindgen::to_value::<StoredLogEvent>(&event.into())
|
||||
.map_err(|_| "Can't serialise event")?,
|
||||
)
|
||||
.map_err(|e| e.message())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn queue_immediate_log(global: &GlobalMemoCell, event: LogEvent) {
|
||||
if let LoggingEngine::Ready { db } = global.log_engine.borrow().deref() {
|
||||
match queue_immediate_log_refutable(db, &event) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
console::log_2(
|
||||
&JsValue::from_str("Error writing event to IndexedDb"),
|
||||
&JsValue::from(e),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn immediate_report_log_frame_refutable(
|
||||
global: GlobalMemoCell,
|
||||
db: &IdbDatabase,
|
||||
frame: TermFrame,
|
||||
) -> Result<(), DomException> {
|
||||
let trans = db.transaction_on_one("logs")?;
|
||||
let store = trans.object_store("logs")?;
|
||||
let curs = store.open_cursor()?.await?;
|
||||
let curs = match curs {
|
||||
None => {
|
||||
let _ = echo_to_term_frame(&global, &frame, "No logs are currently stored.\r\n");
|
||||
return Ok(());
|
||||
}
|
||||
Some(curs) => curs,
|
||||
};
|
||||
let mut event_stats = BTreeMap::<String, u64>::new();
|
||||
loop {
|
||||
if let Ok(ev) = serde_wasm_bindgen::from_value::<StoredLogEvent>(curs.value()) {
|
||||
event_stats
|
||||
.entry(ev.stream)
|
||||
.and_modify(|v| *v += 1)
|
||||
.or_insert(1);
|
||||
};
|
||||
if !curs.continue_cursor()?.await? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let msg = event_stats
|
||||
.iter()
|
||||
.map(|(k, v)| format!("Log stream {} has {} log records.\r\n", k, v))
|
||||
.join("");
|
||||
let _ = echo_to_term_frame(&global, &frame, &msg);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn immediate_report_log_frame_async(
|
||||
global: GlobalMemoCell,
|
||||
db: Arc<IdbDatabase>,
|
||||
frame: TermFrame,
|
||||
) {
|
||||
match immediate_report_log_frame_refutable(global, &db, frame).await {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
console::log_2(
|
||||
&JsValue::from_str("Error reading log stats from IndexedDb"),
|
||||
&JsValue::from(e),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn immediate_report_log_frame(global: &GlobalMemoCell, frame: TermFrame) {
|
||||
let log_engine = global.log_engine.borrow();
|
||||
if let LoggingEngine::Ready { db } = log_engine.deref() {
|
||||
spawn_local(immediate_report_log_frame_async(
|
||||
global.clone(),
|
||||
db.clone(),
|
||||
frame,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
async fn immediate_delete_logs_refutable(
|
||||
global: GlobalMemoCell,
|
||||
db: &IdbDatabase,
|
||||
act: DeleteLogs,
|
||||
) -> Result<(), DomException> {
|
||||
let trans = db.transaction_on_one_with_mode("logs", IdbTransactionMode::Readwrite)?;
|
||||
let store = trans.object_store("logs")?;
|
||||
let idx = store.index("by_stream_timestamp")?;
|
||||
let range = IdbKeyRange::bound(
|
||||
&JsValue::from(
|
||||
[
|
||||
JsValue::from_str(&act.stream),
|
||||
JsValue::from_str(&act.min_date),
|
||||
]
|
||||
.iter()
|
||||
.collect::<Array>(),
|
||||
),
|
||||
&JsValue::from(
|
||||
[
|
||||
JsValue::from_str(&act.stream),
|
||||
JsValue::from_str(&act.max_date),
|
||||
]
|
||||
.iter()
|
||||
.collect::<Array>(),
|
||||
),
|
||||
)?;
|
||||
let cur = match idx.open_key_cursor_with_range(&range)?.await? {
|
||||
None => {
|
||||
let _ = echo_to_term_frame(
|
||||
&global,
|
||||
&act.reply_to,
|
||||
"No logs matched; no logs deleted.\r\n",
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
Some(cur) => cur,
|
||||
};
|
||||
let mut records: u64 = 0;
|
||||
loop {
|
||||
if let Some(pk) = cur.primary_key() {
|
||||
store.delete(&pk)?;
|
||||
records += 1;
|
||||
}
|
||||
if !cur.continue_cursor()?.await? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let _ = echo_to_term_frame(
|
||||
&global,
|
||||
&act.reply_to,
|
||||
&format!("{} logs matched and were deleted.\r\n", records),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn immediate_delete_logs_async(
|
||||
global: GlobalMemoCell,
|
||||
db: Arc<IdbDatabase>,
|
||||
act: DeleteLogs,
|
||||
) {
|
||||
match immediate_delete_logs_refutable(global, &db, act).await {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
console::log_2(
|
||||
&JsValue::from_str("Error deleting logs from IndexedDb"),
|
||||
&JsValue::from(e),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn immediate_delete_logs(global: &GlobalMemoCell, act: DeleteLogs) {
|
||||
let log_engine = global.log_engine.borrow();
|
||||
if let LoggingEngine::Ready { db } = log_engine.deref() {
|
||||
spawn_local(immediate_delete_logs_async(
|
||||
global.clone(),
|
||||
db.clone(),
|
||||
act.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
async fn immediate_download_logs_refutable(
|
||||
global: GlobalMemoCell,
|
||||
db: &IdbDatabase,
|
||||
act: DownloadLogs,
|
||||
) -> Result<(), DomException> {
|
||||
let trans = db.transaction_on_one_with_mode("logs", IdbTransactionMode::Readwrite)?;
|
||||
let store = trans.object_store("logs")?;
|
||||
let idx = store.index("by_stream_timestamp")?;
|
||||
let range = IdbKeyRange::bound(
|
||||
&JsValue::from(
|
||||
[
|
||||
JsValue::from_str(&act.stream),
|
||||
JsValue::from_str(&act.min_date),
|
||||
]
|
||||
.iter()
|
||||
.collect::<Array>(),
|
||||
),
|
||||
&JsValue::from(
|
||||
[
|
||||
JsValue::from_str(&act.stream),
|
||||
JsValue::from_str(&act.max_date),
|
||||
]
|
||||
.iter()
|
||||
.collect::<Array>(),
|
||||
),
|
||||
)?;
|
||||
let cur = match idx.open_key_cursor_with_range(&range)?.await? {
|
||||
None => {
|
||||
let _ = echo_to_term_frame(
|
||||
&global,
|
||||
&act.reply_to,
|
||||
"No logs matched; no logs downloaded.\r\n",
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
Some(cur) => cur,
|
||||
};
|
||||
let mut buf: String = String::new();
|
||||
loop {
|
||||
if let Some(k) = cur.primary_key() {
|
||||
if let Some(Ok(rec)) = store
|
||||
.get(&k)?
|
||||
.await?
|
||||
.map(serde_wasm_bindgen::from_value::<StoredLogEvent>)
|
||||
{
|
||||
buf.push_str(&rec.message);
|
||||
}
|
||||
}
|
||||
if !cur.continue_cursor()?.await? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let buf_bytes = buf.as_bytes();
|
||||
let buf_array = Uint8Array::new_with_length(buf_bytes.len() as u32);
|
||||
buf_array.copy_from(buf_bytes);
|
||||
let mut blobprops: BlobPropertyBag = Default::default();
|
||||
blobprops.type_("text/plain");
|
||||
let blob = Blob::new_with_u8_array_sequence_and_options(
|
||||
&[&buf_array].into_iter().collect::<Array>(),
|
||||
&blobprops,
|
||||
)?;
|
||||
console::log_2(&buf_array, &blob);
|
||||
let url = Url::create_object_url_with_blob(&blob)?;
|
||||
if let Some((doc, body)) = web_sys::window()
|
||||
.and_then(|w| w.document())
|
||||
.and_then(|d| d.body().map(|b| (d, b)))
|
||||
{
|
||||
let el_anchor = JsCast::unchecked_into::<HtmlAnchorElement>(doc.create_element("a")?);
|
||||
body.append_child(&el_anchor)?;
|
||||
el_anchor.set_href(&url);
|
||||
el_anchor.set_download(&format!("{}-logs.txt", act.stream));
|
||||
el_anchor.click();
|
||||
body.remove_child(&el_anchor)?;
|
||||
}
|
||||
// Url::revoke_object_url(&url)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn immediate_download_logs_async(
|
||||
global: GlobalMemoCell,
|
||||
db: Arc<IdbDatabase>,
|
||||
act: DownloadLogs,
|
||||
) {
|
||||
match immediate_download_logs_refutable(global, &db, act).await {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
console::log_2(
|
||||
&JsValue::from_str("Error downloading logs from IndexedDb"),
|
||||
&JsValue::from(e),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn immediate_download_logs(global: &GlobalMemoCell, act: DownloadLogs) {
|
||||
let log_engine = global.log_engine.borrow();
|
||||
if let LoggingEngine::Ready { db } = log_engine.deref() {
|
||||
spawn_local(immediate_download_logs_async(
|
||||
global.clone(),
|
||||
db.clone(),
|
||||
act.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log(global: &GlobalMemoCell, event: &LogEvent) {
|
||||
let mut engine_borrow = global.log_engine.borrow_mut();
|
||||
match engine_borrow.deref_mut() {
|
||||
LoggingEngine::Uninitialised => {
|
||||
*engine_borrow = LoggingEngine::Initialising {
|
||||
backlog: vec![QueuedAction::LogEvent(event.clone())],
|
||||
};
|
||||
drop(engine_borrow);
|
||||
init_logging(global);
|
||||
}
|
||||
LoggingEngine::Initialising { ref mut backlog } => {
|
||||
backlog.push(QueuedAction::LogEvent(event.clone()));
|
||||
}
|
||||
LoggingEngine::Unavailable => {
|
||||
// Assume the user has already been informed it failed, so do nothing.
|
||||
}
|
||||
LoggingEngine::Ready { .. } => {
|
||||
drop(engine_borrow);
|
||||
queue_immediate_log(global, event.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_listing_logs(global: &GlobalMemoCell, send_to: &TermFrame) {
|
||||
let mut engine_borrow = global.log_engine.borrow_mut();
|
||||
match engine_borrow.deref_mut() {
|
||||
LoggingEngine::Uninitialised => {
|
||||
*engine_borrow = LoggingEngine::Initialising {
|
||||
backlog: vec![QueuedAction::ReportOnLogStreams(send_to.clone())],
|
||||
};
|
||||
drop(engine_borrow);
|
||||
init_logging(global);
|
||||
}
|
||||
LoggingEngine::Initialising { ref mut backlog } => {
|
||||
backlog.push(QueuedAction::ReportOnLogStreams(send_to.clone()));
|
||||
}
|
||||
LoggingEngine::Unavailable => {
|
||||
// Assume the user has already been informed it failed, so do nothing.
|
||||
}
|
||||
LoggingEngine::Ready { .. } => {
|
||||
drop(engine_borrow);
|
||||
immediate_report_log_frame(global, send_to.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_deleting_logs(
|
||||
global: &GlobalMemoCell,
|
||||
reply_to: &TermFrame,
|
||||
stream: &str,
|
||||
min_date: &str,
|
||||
max_date: &str,
|
||||
) {
|
||||
let mut engine_borrow = global.log_engine.borrow_mut();
|
||||
let act = DeleteLogs {
|
||||
reply_to: reply_to.clone(),
|
||||
stream: stream.to_owned(),
|
||||
min_date: min_date.to_owned(),
|
||||
max_date: max_date.to_owned(),
|
||||
};
|
||||
match engine_borrow.deref_mut() {
|
||||
LoggingEngine::Uninitialised => {
|
||||
*engine_borrow = LoggingEngine::Initialising {
|
||||
backlog: vec![QueuedAction::DeleteLogs(act)],
|
||||
};
|
||||
drop(engine_borrow);
|
||||
init_logging(global);
|
||||
}
|
||||
LoggingEngine::Initialising { ref mut backlog } => {
|
||||
backlog.push(QueuedAction::DeleteLogs(act));
|
||||
}
|
||||
LoggingEngine::Unavailable => {
|
||||
// Assume the user has already been informed it failed, so do nothing.
|
||||
}
|
||||
LoggingEngine::Ready { .. } => {
|
||||
drop(engine_borrow);
|
||||
immediate_delete_logs(global, act);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_downloading_logs(
|
||||
global: &GlobalMemoCell,
|
||||
reply_to: &TermFrame,
|
||||
stream: &str,
|
||||
min_date: &str,
|
||||
max_date: &str,
|
||||
) {
|
||||
let mut engine_borrow = global.log_engine.borrow_mut();
|
||||
let act = DownloadLogs {
|
||||
reply_to: reply_to.clone(),
|
||||
stream: stream.to_owned(),
|
||||
min_date: min_date.to_owned(),
|
||||
max_date: max_date.to_owned(),
|
||||
};
|
||||
match engine_borrow.deref_mut() {
|
||||
LoggingEngine::Uninitialised => {
|
||||
*engine_borrow = LoggingEngine::Initialising {
|
||||
backlog: vec![QueuedAction::DownloadLogs(act)],
|
||||
};
|
||||
drop(engine_borrow);
|
||||
init_logging(global);
|
||||
}
|
||||
LoggingEngine::Initialising { ref mut backlog } => {
|
||||
backlog.push(QueuedAction::DownloadLogs(act));
|
||||
}
|
||||
LoggingEngine::Unavailable => {
|
||||
// Assume the user has already been informed it failed, so do nothing.
|
||||
}
|
||||
LoggingEngine::Ready { .. } => {
|
||||
drop(engine_borrow);
|
||||
immediate_download_logs(global, act);
|
||||
}
|
||||
}
|
||||
}
|
@ -147,6 +147,15 @@ pub fn install_lua_globals(
|
||||
)
|
||||
.map_err(|_| Error::msg("Can't add command"))?;
|
||||
};
|
||||
($sym: ident, $name: literal) => {
|
||||
cmd_table
|
||||
.set(
|
||||
ctx,
|
||||
ctx.intern_static($name.as_bytes()),
|
||||
$sym(ctx, &global_memo, &global_layout),
|
||||
)
|
||||
.map_err(|_| Error::msg("Can't add command"))?;
|
||||
};
|
||||
}
|
||||
macro_rules! register_stateless_command {
|
||||
($sym: ident) => {
|
||||
@ -171,11 +180,15 @@ pub fn install_lua_globals(
|
||||
register_command!(close_mud);
|
||||
register_command!(connect_mud);
|
||||
register_stateless_command!(create_match_table);
|
||||
register_command!(cmd_delete_logs, "deletelogs");
|
||||
register_command!(delete_mud);
|
||||
register_command!(cmd_download_logs, "downloadlogs");
|
||||
register_command!(echo);
|
||||
register_command!(echo_frame);
|
||||
register_command!(echo_frame_raw);
|
||||
register_command!(hsplit);
|
||||
register_command!(cmd_list_logs, "listlogs");
|
||||
register_command!(mud_log, "log");
|
||||
register_command!(panel_merge);
|
||||
register_command!(sendmud_raw);
|
||||
register_stateless_command!(mud_trigger, "untrigger");
|
||||
|
@ -11,6 +11,7 @@ use yew::UseStateSetter;
|
||||
use crate::{
|
||||
command_handler::execute_queue,
|
||||
id_intern::{intern_id, unintern_id},
|
||||
logging::{start_deleting_logs, start_downloading_logs, start_listing_logs},
|
||||
match_table::{create_match_table, match_table_add, match_table_remove},
|
||||
telnet::{parse_telnet_buf, TelnetOutput},
|
||||
websocket::{connect_websocket, send_message_to_mud, WebSocketId},
|
||||
@ -148,7 +149,7 @@ pub(super) fn connect_mud<'gc>(
|
||||
}
|
||||
break v;
|
||||
};
|
||||
let name: Value<'gc> = ctx.intern(&name.as_bytes()).into();
|
||||
let name: Value<'gc> = ctx.intern(name.as_bytes()).into();
|
||||
|
||||
let muds: Table = ctx.get_global("muds")?;
|
||||
if !muds.get_value(ctx, name).is_nil() {
|
||||
@ -392,10 +393,6 @@ pub(super) fn mudoutput_line<'gc>(
|
||||
.unwrap_or_else(|| Ok(ctx.get_global::<Table>("frames")?.get(ctx, 1_i64)?))?
|
||||
.get(ctx, "frame")?;
|
||||
|
||||
console::log_1(&JsValue::from_str(&format!(
|
||||
"Line to match: {:?}",
|
||||
line.as_bytes()
|
||||
)));
|
||||
let seq = async_sequence(&ctx, |locals, mut seq| {
|
||||
let frameroutes: Vec<StashedTable> = frameroutes
|
||||
.iter()
|
||||
@ -635,3 +632,166 @@ pub(super) fn mud_untrigger(ctx: Context<'_>) -> Callback<'_> {
|
||||
Ok(piccolo::CallbackReturn::Sequence(seq))
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn mud_log<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
global_memo: &GlobalMemoCell,
|
||||
_global_layout: &UseStateSetter<GlobalLayoutCell>,
|
||||
) -> Callback<'gc> {
|
||||
let global_memo = global_memo.clone();
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let name: String = stack.from_front(ctx)?;
|
||||
|
||||
let muds: Table = ctx.get_global("muds")?;
|
||||
let mud_value: Value = muds.get(ctx, name)?;
|
||||
if mud_value.is_nil() {
|
||||
Err(Error::msg(
|
||||
"Attempt to delete MUD connection that wasn't found",
|
||||
))?
|
||||
}
|
||||
|
||||
let log_dest: String = stack.from_back(ctx)?;
|
||||
let log_dest = if log_dest == "none" || log_dest == "off" {
|
||||
None
|
||||
} else {
|
||||
Some(log_dest)
|
||||
};
|
||||
|
||||
let socket_id = try_unwrap_socketid(ctx, &mud_value)?;
|
||||
match global_memo.ws_registry.borrow_mut().get_mut(&socket_id) {
|
||||
None => Err(Error::msg("That MUD connection doesn't exist."))?,
|
||||
Some(v) => {
|
||||
v.log_dest = log_dest;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CallbackReturn::Return)
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn cmd_list_logs<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
global_memo: &GlobalMemoCell,
|
||||
_global_layout: &UseStateSetter<GlobalLayoutCell>,
|
||||
) -> Callback<'gc> {
|
||||
let global_memo = global_memo.clone();
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, _stack| {
|
||||
let frame = try_unwrap_frame(
|
||||
ctx,
|
||||
&ctx.get_global::<Table>("info")?
|
||||
.get::<&str, Value>(ctx, "current_frame")?,
|
||||
)?;
|
||||
start_listing_logs(&global_memo, &frame);
|
||||
Ok(CallbackReturn::Return)
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn cmd_delete_logs<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
global_memo: &GlobalMemoCell,
|
||||
_global_layout: &UseStateSetter<GlobalLayoutCell>,
|
||||
) -> Callback<'gc> {
|
||||
let global_memo = global_memo.clone();
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let frame = try_unwrap_frame(
|
||||
ctx,
|
||||
&ctx.get_global::<Table>("info")?
|
||||
.get::<&str, Value>(ctx, "current_frame")?,
|
||||
)?;
|
||||
let stream = String::from_utf8(
|
||||
piccolo::String::from_value(
|
||||
ctx,
|
||||
stack
|
||||
.pop_front()
|
||||
.ok_or_else(|| anyhow::Error::msg("No stream name argument given"))?,
|
||||
)?
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
)?;
|
||||
let (min_date, max_date) = match stack.len() {
|
||||
2 => {
|
||||
let min_date = String::from_utf8(
|
||||
piccolo::String::from_value(ctx, stack.pop_front().unwrap())?
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
)?;
|
||||
let max_date = String::from_utf8(
|
||||
piccolo::String::from_value(ctx, stack.pop_front().unwrap())?
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
)?;
|
||||
(min_date, max_date)
|
||||
}
|
||||
1 => {
|
||||
let max_date = String::from_utf8(
|
||||
piccolo::String::from_value(ctx, stack.pop_front().unwrap())?
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
)?;
|
||||
("0000-00-00".to_owned(), max_date)
|
||||
}
|
||||
0 => ("0000-00-00".to_owned(), "9999-12-31".to_owned()),
|
||||
_ => Err(anyhow::Error::msg(
|
||||
"At most three arguments expected: stream min-date max-date",
|
||||
))?,
|
||||
};
|
||||
|
||||
start_deleting_logs(&global_memo, &frame, &stream, &min_date, &max_date);
|
||||
Ok(CallbackReturn::Return)
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn cmd_download_logs<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
global_memo: &GlobalMemoCell,
|
||||
_global_layout: &UseStateSetter<GlobalLayoutCell>,
|
||||
) -> Callback<'gc> {
|
||||
let global_memo = global_memo.clone();
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let frame = try_unwrap_frame(
|
||||
ctx,
|
||||
&ctx.get_global::<Table>("info")?
|
||||
.get::<&str, Value>(ctx, "current_frame")?,
|
||||
)?;
|
||||
let stream = String::from_utf8(
|
||||
piccolo::String::from_value(
|
||||
ctx,
|
||||
stack
|
||||
.pop_front()
|
||||
.ok_or_else(|| anyhow::Error::msg("No stream name argument given"))?,
|
||||
)?
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
)?;
|
||||
let (min_date, max_date) = match stack.len() {
|
||||
2 => {
|
||||
let min_date = String::from_utf8(
|
||||
piccolo::String::from_value(ctx, stack.pop_front().unwrap())?
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
)?;
|
||||
let max_date = String::from_utf8(
|
||||
piccolo::String::from_value(ctx, stack.pop_front().unwrap())?
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
)?;
|
||||
(min_date, max_date)
|
||||
}
|
||||
1 => {
|
||||
let max_date = String::from_utf8(
|
||||
piccolo::String::from_value(ctx, stack.pop_front().unwrap())?
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
)?;
|
||||
("0000-00-00".to_owned(), max_date)
|
||||
}
|
||||
0 => ("0000-00-00".to_owned(), "9999-12-31".to_owned()),
|
||||
_ => Err(anyhow::Error::msg(
|
||||
"At most three arguments expected: stream min-date max-date",
|
||||
))?,
|
||||
};
|
||||
|
||||
start_downloading_logs(&global_memo, &frame, &stream, &min_date, &max_date);
|
||||
Ok(CallbackReturn::Return)
|
||||
})
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
|
||||
use logging::LoggingEngine;
|
||||
use parsing::ParsedCommand;
|
||||
use term_split::TermSplit;
|
||||
use yew::prelude::*;
|
||||
@ -9,6 +10,7 @@ use yew::prelude::*;
|
||||
pub mod command_handler;
|
||||
pub mod id_intern;
|
||||
pub mod lineengine;
|
||||
pub mod logging;
|
||||
pub mod lua_engine;
|
||||
pub mod match_table;
|
||||
pub mod parsing;
|
||||
@ -29,6 +31,7 @@ pub struct GlobalMemoState {
|
||||
lua_engine: RefCell<LuaState>,
|
||||
ws_registry: RefCell<RegisteredWebSockets>,
|
||||
command_queue: RefCell<VecDeque<(TermFrame, ParsedCommand)>>,
|
||||
log_engine: RefCell<LoggingEngine>,
|
||||
|
||||
// A cache of the latest layout info (separate from the state).
|
||||
// Updating this doesn't force a relayout, so only update the cache when
|
||||
@ -72,6 +75,7 @@ fn app() -> Html {
|
||||
command_queue: VecDeque::new().into(),
|
||||
lua_engine: LuaState::setup().expect("Can create interpreter").into(),
|
||||
layout: RefCell::new((*global_layout).clone()),
|
||||
log_engine: RefCell::new(Default::default()),
|
||||
});
|
||||
use_memo((), |_| {
|
||||
install_lua_globals(&global_memo, global_layout.setter())
|
||||
|
@ -3,6 +3,7 @@ use std::collections::BTreeMap;
|
||||
use gc_arena::Collect;
|
||||
use serde::Deserialize;
|
||||
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
|
||||
use wasm_bindgen_futures::js_sys::Date;
|
||||
use web_sys::{
|
||||
console,
|
||||
js_sys::{ArrayBuffer, JsString, Uint8Array},
|
||||
@ -10,6 +11,7 @@ use web_sys::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
logging::{self, LogEvent},
|
||||
lua_engine::muds::{handle_websocket_close_log_err, handle_websocket_output_log_err},
|
||||
GlobalMemoCell,
|
||||
};
|
||||
@ -23,6 +25,7 @@ pub struct WebSocketData {
|
||||
pub closed: bool,
|
||||
pub url: String,
|
||||
pub retained_closures: Option<(Closure<dyn FnMut(MessageEvent)>, Closure<dyn FnMut()>)>,
|
||||
pub log_dest: Option<String>,
|
||||
}
|
||||
pub type RegisteredWebSockets = BTreeMap<WebSocketId, WebSocketData>;
|
||||
|
||||
@ -52,6 +55,7 @@ pub fn connect_websocket(
|
||||
closed: false,
|
||||
url: url.to_owned(),
|
||||
retained_closures: None,
|
||||
log_dest: None,
|
||||
};
|
||||
data.connection.set_binary_type(BinaryType::Arraybuffer);
|
||||
|
||||
@ -63,6 +67,23 @@ pub fn connect_websocket(
|
||||
let data_closure: Closure<dyn FnMut(MessageEvent)> = Closure::new(move |ev: MessageEvent| {
|
||||
let data = ev.data();
|
||||
if data.has_type::<ArrayBuffer>() {
|
||||
let log_dest = data_globals
|
||||
.ws_registry
|
||||
.borrow()
|
||||
.get(&data_new_id)
|
||||
.and_then(|d| d.log_dest.clone());
|
||||
if let Some(log_dest) = &log_dest {
|
||||
let str_rendering =
|
||||
String::from_utf8_lossy(&Uint8Array::new(&data).to_vec()).to_string();
|
||||
logging::log(
|
||||
&data_globals,
|
||||
&LogEvent {
|
||||
log_stream: log_dest.clone(),
|
||||
timestamp: Date::new_0(),
|
||||
message: format!("R {}", &str_rendering),
|
||||
},
|
||||
);
|
||||
}
|
||||
handle_websocket_output_log_err(
|
||||
&data_new_id,
|
||||
&data_globals,
|
||||
@ -126,6 +147,16 @@ pub fn send_message_to_mud(
|
||||
None => Err(anyhow::Error::msg("MUD connection not found")),
|
||||
Some(sock_data) if sock_data.closed => Err(anyhow::Error::msg("MUD connection is closed")),
|
||||
Some(sock_data) => {
|
||||
if let Some(log_dest) = &sock_data.log_dest {
|
||||
logging::log(
|
||||
global,
|
||||
&LogEvent {
|
||||
log_stream: log_dest.clone(),
|
||||
timestamp: Date::new_0(),
|
||||
message: format!("W {}", String::from_utf8_lossy(msg)),
|
||||
},
|
||||
);
|
||||
}
|
||||
sock_data.connection.send_with_u8_array(msg).map_err(|e| {
|
||||
e.dyn_into::<DomException>()
|
||||
.map(|e| anyhow::Error::msg(e.message()))
|
||||
|
Loading…
Reference in New Issue
Block a user