-
Notifications
You must be signed in to change notification settings - Fork 527
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
226 additions
and
55 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
use napi::bindgen_prelude::*; | ||
use napi::{Env, JsFunction, JsObject, JsUndefined, Task}; | ||
|
||
/// todo: plumb through SDK's log messages? it's currently using simple_logger when not compiled | ||
/// todo: for ios or android. | ||
|
||
pub fn setup_logging(env: Env) -> Result<Console> { | ||
let (tx, rx) = std::sync::mpsc::channel::<LogMessage>(); | ||
let logger = Logger { rx: Some(rx) }; | ||
let Ok(_async_task) = env.spawn(logger) else { | ||
return Err(Error::from_reason("failed to spawn logger")); | ||
}; | ||
|
||
let logger_thread_id = std::thread::current().id(); | ||
let console = Console::new(tx); | ||
let panic_console = console.clone(); | ||
std::panic::set_hook(Box::new(move |panic_info| { | ||
if logger_thread_id == std::thread::current().id() { | ||
// logger is (probably) running on the currently panicking thread, | ||
// so we can't use it to log to JS. this at least shows up in stderr. | ||
eprintln!("PANIC MAIN {}", panic_info.to_string().as_str()); | ||
eprintln!("MAIN PANIC {}", std::backtrace::Backtrace::force_capture().to_string().as_str()); | ||
} else { | ||
panic_console.error("PANIC", format!("thread {:?} {}", std::thread::current().name(), panic_info.to_string().as_str()).as_str()); | ||
panic_console.error("PANIC", std::backtrace::Backtrace::force_capture().to_string().as_str()); | ||
} | ||
})); | ||
|
||
Ok(console) | ||
} | ||
|
||
/// A way for the rust code to log messages to the main applications log files | ||
/// without having to deal with obtaining a reference to console each time. | ||
#[derive(Clone)] | ||
pub struct Console { | ||
tx: std::sync::mpsc::Sender<LogMessage>, | ||
} | ||
|
||
impl Console { | ||
pub fn new(tx: std::sync::mpsc::Sender<LogMessage>) -> Self { | ||
Self { tx } | ||
} | ||
pub fn log(&self, tag: &str, message: &str) { | ||
// todo: if the logger dies before us, print message to stdout instead and panic? | ||
let _ = self.tx.send(LogMessage { | ||
level: LogLevel::Log, | ||
tag: tag.into(), | ||
message: message.into(), | ||
}); | ||
} | ||
pub fn warn(&self, tag: &str, message: &str) { | ||
let _ = self.tx.send(LogMessage { | ||
level: LogLevel::Warn, | ||
tag: tag.into(), | ||
message: message.into(), | ||
}); | ||
} | ||
|
||
pub fn error(&self, tag: &str, message: &str) { | ||
let _ = self.tx.send(LogMessage { | ||
level: LogLevel::Error, | ||
tag: tag.into(), | ||
message: message.into(), | ||
}); | ||
} | ||
} | ||
|
||
/// The part of the logging setup that receives log messages from the rust log | ||
/// {@link struct Console} and forwards them to the node environment to log. | ||
struct Logger { | ||
/// This is an option because we need to take it from the old instance before | ||
/// rescheduling the listen job with a new one. | ||
rx: Option<std::sync::mpsc::Receiver<LogMessage>>, | ||
} | ||
|
||
impl Logger { | ||
fn execute_log(&self, env: Env, log_message: LogMessage) { | ||
let globals = env.get_global() | ||
.expect("no globals in env"); | ||
let console: JsObject = globals.get_named_property("console") | ||
.expect("console property not found"); | ||
|
||
let formatted_message = format!("[{} {}] {}", log_message.marker(), log_message.tag, log_message.message); | ||
let js_string: napi::JsString = env.create_string_from_std(formatted_message) | ||
.expect("could not create string"); | ||
|
||
let js_error: JsFunction = console.get_named_property(log_message.method()) | ||
.expect("logging fn not found"); | ||
js_error.call(None, &[js_string]) | ||
.expect("logging failed"); | ||
} | ||
} | ||
|
||
impl Task for Logger { | ||
type Output = LogMessage; | ||
type JsValue = JsUndefined; | ||
|
||
/// runs on the libuv thread pool. | ||
fn compute(&mut self) -> Result<Self::Output> { | ||
if let Some(rx) = &self.rx { | ||
Ok(rx.recv().unwrap_or_else(|_| LogMessage { | ||
level: LogLevel::Finish, | ||
tag: "Logger".to_string(), | ||
message: "channel closed, logger finished".to_string(), | ||
})) | ||
} else { | ||
// should not happen - each Logger instance listens for exactly one message and then | ||
// gets dropped and reincarnated. | ||
Ok(LogMessage { | ||
level: LogLevel::Error, | ||
tag: "Logger".to_string(), | ||
message: "rx not available, already moved".to_string(), | ||
}) | ||
} | ||
} | ||
|
||
fn resolve(&mut self, env: Env, output: Self::Output) -> Result<Self::JsValue> { | ||
let level = output.level; | ||
self.execute_log(env, output); | ||
if level != LogLevel::Finish { | ||
// we only have a &mut self, so can't revive ourselves directly. | ||
// I guess this is reincarnation. | ||
let rx = self.rx.take(); | ||
let _promise = env.spawn(Logger { rx }); | ||
} | ||
Ok(env.get_undefined()?) | ||
} | ||
} | ||
|
||
/// determines the urgency and some formatting of the log message | ||
#[derive(Eq, PartialEq, Copy, Clone)] | ||
enum LogLevel { | ||
/// used if we want to log the fact that all consoles have been dropped (there will not be any more log messages) | ||
Finish, | ||
Log, | ||
Warn, | ||
Error, | ||
} | ||
|
||
struct LogMessage { | ||
pub level: LogLevel, | ||
pub message: String, | ||
pub tag: String, | ||
} | ||
|
||
impl LogMessage { | ||
/// get a prefix for labeling the log level in cases where it's | ||
/// not obvious from terminal colors or similar | ||
pub fn marker(&self) -> &str { | ||
match self.level { | ||
LogLevel::Finish | LogLevel::Log => "I", | ||
LogLevel::Warn => "W", | ||
LogLevel::Error => "E", | ||
} | ||
} | ||
|
||
/// the name of the logging method to use for each log level. | ||
/// very js-specific. | ||
pub fn method(&self) -> &str { | ||
match self.level { | ||
LogLevel::Finish | LogLevel::Log => "log", | ||
LogLevel::Warn => "warn", | ||
LogLevel::Error => "error", | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
use crate::console::setup_logging; | ||
use crate::console::Console; | ||
use napi::bindgen_prelude::*; | ||
use std::sync::atomic::{AtomicBool, Ordering}; | ||
use std::time::Duration; | ||
use std::{panic, thread}; | ||
|
||
/// if set to true, an importer is already created for this instance of the addon. | ||
static IMPORTER_INIT: AtomicBool = AtomicBool::new(false); | ||
|
||
#[napi] | ||
pub struct ImapImporter { | ||
console: Console, | ||
} | ||
|
||
const TAG: &'static str = file!(); | ||
|
||
#[napi] | ||
impl ImapImporter { | ||
/// only to be used once and only from the javascript side! | ||
#[napi(factory)] | ||
pub fn setup(env: Env) -> Result<Self> { | ||
if !IMPORTER_INIT.swap(true, Ordering::Relaxed) { | ||
let console = setup_logging(env)?; | ||
Ok(ImapImporter { console }) | ||
} else { | ||
Err(Error::from_reason("already created an importer!")) | ||
} | ||
} | ||
|
||
#[napi] | ||
pub fn log_that(&self, that: String) -> Result<()> { | ||
self.console.log("that", &that); | ||
Err(Error::from_reason("done")) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters