Merge branch 'master' into support_references

This commit is contained in:
vms 2021-03-01 15:32:58 +03:00
commit e577d6b0c8
4 changed files with 292 additions and 29 deletions

View File

@ -35,6 +35,7 @@ mod call_parameters;
mod export_allocator;
#[cfg(any(feature = "debug", feature = "logger"))]
mod logger;
pub mod mounted_binary;
mod result;
pub use call_parameters::CallParameters;

View File

@ -22,13 +22,13 @@
//! This example initializes [`WasmLogger`] with setting log level.
//! Macros from crate [`log`] are used as a logging facade.
//!
//! ```
//! use fluence::sdk::*;
//! ```ignore
//! use fluence::logger;
//! use log::{error, trace};
//! use simple_logger;
//!
//! fn main() {
//! logger::WasmLogger::new()
//! logger::WasmLoggerBuilder::new()
//! .with_log_leve(log::Level::Info)
//! .build()
//! .unwrap();
@ -42,19 +42,33 @@
//! [`WasmLogger`]: struct.WasmLogger.html
//! [`log`]: https://docs.rs/log
use log::Level as LogLevel;
use log::LevelFilter;
use std::collections::HashMap;
/// By default, logger will be initialized with log level from this environment variable.
pub const WASM_LOG_ENV_NAME: &'static str = "WASM_LOG";
pub const WASM_LOG_ENV_NAME: &str = "WASM_LOG";
/// If WASM_LOG_ENV isn't set, then this level will be used as the default.
const WASM_DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Info;
const WASM_DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Info;
/// Mapping from logging namespace string to its bitmask.
/// TODO: use i64 for bitmask when wasmpack/bindgen issue with i64 is fixed.
/// Currently, i64 doesn't work on some versions of V8 because log_utf8_string function
/// isn't marked as #[wasm_bindgen]. In result, TS/JS code throws 'TypeError' on every log.
pub type TargetMap = std::collections::HashMap<&'static str, i32>;
pub type TargetMap = HashMap<&'static str, i32>;
/// This structure is used to save information about particular log level for a particular module.
#[derive(Debug)]
struct LogDirective {
module_name: String,
level: LevelFilter,
}
impl LogDirective {
pub fn new(module_name: String, level: LevelFilter) -> Self {
Self { module_name, level }
}
}
/// The Wasm Logger.
///
@ -68,14 +82,15 @@ pub type TargetMap = std::collections::HashMap<&'static str, i32>;
/// [`Log`]: https://docs.rs/log/0.4.11/log/trait.Log.html
struct WasmLogger {
target_map: TargetMap,
modules_directives: Vec<LogDirective>,
default_log_level: LevelFilter,
}
/// The Wasm logger builder.
///
/// Build logger for the Fluence network, allows specifying target map and log level while building.
pub struct WasmLoggerBuilder {
target_map: TargetMap,
log_level: log::Level,
wasm_logger: WasmLogger,
}
impl WasmLoggerBuilder {
@ -85,27 +100,38 @@ impl WasmLoggerBuilder {
pub fn new() -> Self {
use std::str::FromStr;
let log_level = std::env::var(WASM_LOG_ENV_NAME)
let default_log_level = std::env::var(WASM_LOG_ENV_NAME)
.map_or(WASM_DEFAULT_LOG_LEVEL, |log_level_str| {
LogLevel::from_str(&log_level_str).unwrap_or(WASM_DEFAULT_LOG_LEVEL)
LevelFilter::from_str(&log_level_str).unwrap_or(WASM_DEFAULT_LOG_LEVEL)
});
Self {
log_level,
target_map: <_>::default(),
}
let wasm_logger = WasmLogger {
target_map: HashMap::new(),
modules_directives: Vec::new(),
default_log_level,
};
Self { wasm_logger }
}
/// Set the log level.
pub fn with_log_level(mut self, level: log::Level) -> Self {
self.log_level = level;
pub fn with_log_level(mut self, level: LevelFilter) -> Self {
self.wasm_logger.default_log_level = level;
self
}
/// Set mapping between logging targets and numbers.
/// Used to efficiently enable & disable logs per target on the host.
pub fn with_target_map(mut self, map: TargetMap) -> Self {
self.target_map = map;
self.wasm_logger.target_map = map;
self
}
pub fn filter(mut self, module_name: impl Into<String>, level: LevelFilter) -> Self {
let module_name = module_name.into();
let log_directive = LogDirective::new(module_name, level);
self.wasm_logger.modules_directives.push(log_directive);
self
}
@ -114,36 +140,65 @@ impl WasmLoggerBuilder {
/// This method is a last one in this builder chain and MUST be called to set logger up.
/// Returns a error
///
/// ```
/// ```ignore
/// # use fluence::logger;
/// # use log::info;
/// #
/// # fn main() {
/// logger::WasmLogger::new()
/// .with_log_level(log::Level::Trace)
/// logger::WasmLoggerBuilder::new()
/// .with_log_level(log::LevelFilter::Trace)
/// .with_target_map(<_>::default())
/// .build()
/// .unwrap();
/// # }
/// ```
pub fn build(self) -> Result<(), log::SetLoggerError> {
let Self {
log_level,
target_map,
} = self;
pub fn build(mut self) -> Result<(), log::SetLoggerError> {
let max_level = self.max_log_level();
self.sort_directives();
let wasm_logger = WasmLogger { target_map };
let Self { wasm_logger } = self;
log::set_boxed_logger(Box::new(wasm_logger))?;
log::set_max_level(log_level.to_level_filter());
log::set_max_level(max_level);
Ok(())
}
/// Sort supplied directive ny length of module names to make more efficient lookup at runtime.
fn sort_directives(&mut self) {
self.wasm_logger.modules_directives.sort_by(|l, r| {
let llen = l.module_name.len();
let rlen = r.module_name.len();
rlen.cmp(&llen)
});
}
fn max_log_level(&self) -> log::LevelFilter {
let default_level = self.wasm_logger.default_log_level;
let max_filter_level = self
.wasm_logger
.modules_directives
.iter()
.map(|d| d.level)
.max()
.unwrap_or(LevelFilter::Off);
std::cmp::max(default_level, max_filter_level)
}
}
impl log::Log for WasmLogger {
#[inline]
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
metadata.level() <= log::max_level()
let target = metadata.target();
for directive in self.modules_directives.iter() {
if target.starts_with(&directive.module_name) {
return metadata.level() <= directive.level;
}
}
metadata.level() <= self.default_log_level
}
#[inline]
@ -204,3 +259,91 @@ fn level_from_i32(level: i32) -> log::Level {
_ => log::Level::max(),
}
}
#[cfg(test)]
mod tests {
use super::WasmLogger;
use super::LogDirective;
use super::WasmLoggerBuilder;
use log::LevelFilter;
use log::Log;
use std::collections::HashMap;
fn create_metadata(module_name: &str, level: log::Level) -> log::Metadata<'_> {
log::MetadataBuilder::new()
.level(level)
.target(module_name)
.build()
}
#[test]
fn enabled_by_module_name() {
let module_1_name = "module_1";
let module_2_name = "module_2";
let modules_directives = vec![
LogDirective::new(module_1_name.to_string(), LevelFilter::Info),
LogDirective::new(module_2_name.to_string(), LevelFilter::Warn),
];
let logger = WasmLogger {
target_map: HashMap::new(),
modules_directives,
default_log_level: LevelFilter::Error,
};
let allowed_metadata = create_metadata(module_1_name, log::Level::Info);
assert!(logger.enabled(&allowed_metadata));
let allowed_metadata = create_metadata(module_1_name, log::Level::Warn);
assert!(logger.enabled(&allowed_metadata));
let allowed_metadata = create_metadata(module_2_name, log::Level::Warn);
assert!(logger.enabled(&allowed_metadata));
let not_allowed_metadata = create_metadata(module_1_name, log::Level::Debug);
assert!(!logger.enabled(&not_allowed_metadata));
let not_allowed_metadata = create_metadata(module_2_name, log::Level::Info);
assert!(!logger.enabled(&not_allowed_metadata));
}
#[test]
fn default_log_level() {
let modules_directives = vec![LogDirective::new("module_1".to_string(), LevelFilter::Info)];
let logger = WasmLogger {
target_map: HashMap::new(),
modules_directives,
default_log_level: LevelFilter::Warn,
};
let module_name = "some_module";
let allowed_metadata = create_metadata(module_name, log::Level::Warn);
assert!(logger.enabled(&allowed_metadata));
let not_allowed_metadata = create_metadata(module_name, log::Level::Info);
assert!(!logger.enabled(&not_allowed_metadata));
}
#[test]
fn longest_directive_first() {
let module_1_name = "module_1";
let module_2_name = "module_1::some_name::func_name";
WasmLoggerBuilder::new()
.filter(module_1_name, LevelFilter::Info)
.filter(module_2_name, LevelFilter::Warn)
.build()
.unwrap();
let logger = log::logger();
let allowed_metadata = create_metadata(module_1_name, log::Level::Info);
assert!(logger.enabled(&allowed_metadata));
let not_allowed_metadata = create_metadata(module_2_name, log::Level::Info);
assert!(!logger.enabled(&not_allowed_metadata));
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* 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 fluence_sdk_macro::fce;
use serde::Serialize;
use serde::Deserialize;
pub const SUCCESS_CODE: i32 = 0;
/// Describes result of calling a CLI service.
#[fce]
#[derive(Clone, PartialEq, Default, Eq, Debug, Serialize, Deserialize)]
pub struct Result {
/// Return process exit code or host execution error code, where SUCCESS_CODE means success.
pub ret_code: i32,
/// Contains the string representation of an error, if ret_code != SUCCESS_CODE.
pub error: String,
/// The data that the process wrote to stdout.
pub stdout: Vec<u8>,
/// The data that the process wrote to stderr.
pub stderr: Vec<u8>,
}
/// The same as the Result, but stdout and stderr are utf8 strings.
#[fce]
#[derive(Clone, PartialEq, Default, Eq, Debug, Serialize, Deserialize)]
pub struct StringResult {
/// Return process exit code or host execution error code, where SUCCESS_CODE means success.
pub ret_code: i32,
/// Contains the string representation of an error, if ret_code != SUCCESS_CODE.
pub error: String,
/// The data that the process wrote to stdout.
pub stdout: String,
/// The data that the process wrote to stderr.
pub stderr: String,
}
impl Result {
/// Create a new failure MountedBinaryResult from the provided ret_code.
pub fn from_error(ret_code: i32, error: impl Into<String>) -> Self {
Self {
ret_code,
error: error.into(),
stdout: Vec::new(),
stderr: Vec::new(),
}
}
/// Return true, if this Result represents a success result, otherwise false.
pub fn is_success(&self) -> bool {
self.ret_code == SUCCESS_CODE
}
/// This function tries to transform a result to the string representation.
/// Internally, It checks ret_code and returns either Some(Ok(stdout)) if it was SUCCESS_CODE
/// or Some(Err(error)) otherwise. None is returned if stdout or stderr contains non valid
/// UTF8 string.
pub fn into_std(self) -> Option<std::result::Result<String, String>> {
if self.ret_code == SUCCESS_CODE {
let stdout = String::from_utf8(self.stdout).ok()?;
Some(Ok(stdout))
} else {
let stderr = std::str::from_utf8(&self.stderr).ok()?;
Some(Ok(format!("error: {}, stderr: {}", self.error, stderr)))
}
}
/// This function tries to represent a result as a string.
/// Internally, It checks ret_code and returns either Some(Ok(stdout)) if it was SUCCESS_CODE
/// or Some(Err(error)) otherwise. None is returned if stdout or stderr contains non valid
/// UTF8 string.
pub fn as_std(&self) -> Option<std::result::Result<String, String>> {
if self.ret_code == SUCCESS_CODE {
let stdout = String::from_utf8(self.stdout.clone()).ok()?;
Some(Ok(stdout))
} else {
let stderr = std::str::from_utf8(&self.stderr).ok()?;
Some(Ok(format!("error: {}, stderr: {}", self.error, stderr)))
}
}
pub fn stringify(&self) -> Option<StringResult> {
let stdout = String::from_utf8(self.stdout.clone()).ok()?;
let stderr = String::from_utf8(self.stderr.clone()).ok()?;
let string_result = StringResult {
ret_code: self.ret_code,
error: self.error.clone(),
stdout,
stderr,
};
Some(string_result)
}
}

View File

@ -79,6 +79,10 @@ pub use fluence_sdk_main::WasmLoggerBuilder;
#[cfg(feature = "logger")]
pub use fluence_sdk_main::TargetMap;
pub use fluence_sdk_main::mounted_binary::Result as MountedBinaryResult;
pub use fluence_sdk_main::mounted_binary::StringResult as MountedBinaryStringResult;
pub use fluence_sdk_main::mounted_binary::SUCCESS_CODE as BINARY_SUCCESS_CODE;
/// These API functions are intended for internal usage in generated code.
/// Normally, you shouldn't use them.
pub mod internal {