调试
在所有软件开发中,调试都是必不可少的,区块链也不例外。 大部分用于通用目的的Rust调试工具,也同样适用于Substrate。 但是,由于Substrate runtime是在no_std
环境中运行的,进行调试时会有一些限制。
FRAME的调试 & 日志工具
The FRAME Support debug
module contains macros and functions that make it possible to print logs out of runtime code.
从Native Runtime打印日志
For performance-preserving, native-only debugging, use the macros in the frame_support::debug::native
module.
pub fn do_something(origin) -> DispatchResult {
print("Execute do_something");
let who = ensure_signed(origin)?;
let my_val: u32 = 777;
Something::put(my_val);
frame_support::debug::native::debug!("called by {:?}", who);
Self::deposit_event(RawEvent::SomethingStored(my_val, who));
Ok(())
}
The frame_support::debug::native::debug!
macro avoids extra overhead in Wasm runtimes, but is only effective when the runtime is executing in native mode. 为了查看这些日志消息,必须给节点配置正确的日志目标。 默认情况下,日志目标的名称与包含了日志消息的crate的名称保持一致。
./target/release/node-template --dev -lpallet_template=debug
debug!
宏接收了一个可选参数target
,于指定日志目标名称。
frame_support::debug::native::debug!(target: "customTarget", "called by {:?}", who);
从Wasm Runtime打印日志
可以通过牺牲性能以换取Wasm runtime输出日志记录。
pub fn do_something(origin) -> DispatchResult {
print("Execute do_something");
let who = ensure_signed(origin)?;
let my_val: u32 = 777;
Something::put(my_val);
frame_support::debug::RuntimeLogger::init();
frame_support::debug::debug!("called by {:?}", who);
Self::deposit_event(RawEvent::SomethingStored(my_val, who));
Ok(())
}
Printable Trait
Printable trait提供了runtime在no_std
和std
两种环境中进行打印的一种方式。 The print
function works with any type that implements the <a href="https://substrate.dev/rustdocs/v3.0.0/sp_runtime/traits/trait. Substrate默认对某些类型 (u8
, u32
, u64
, usize
, &[u8]
, &str
) 实现了此特征。 当然也可把它应用到自定义类型上。 以下示例用node-template作为代码样版,在一个pallet的Error
类型上实现了printable trait。
use sp_runtime::traits::Printable;
use sp_runtime::print;
// The pallet's errors
decl_error! {
pub enum Error for Module<T: Config> {
/// Value was None
NoneValue,
/// Value reached maximum and cannot be incremented further
StorageOverflow,
}
}
impl<T: Config> Printable for Error<T> {
fn print(&self) {
match self {
Error::NoneValue => "Invalid Value".print(),
Error::StorageOverflow => "Value Exceeded and Overflowed".print(),
_ => "Invalid Error Case".print(),
}
}
}
/// takes no parameters, attempts to increment storage value, and possibly throws an error
pub fn cause_error(origin) -> dispatch::DispatchResult {
// Check it was signed and get the signer. See also: ensure_root and ensure_none
let _who = ensure_signed(origin)?;
print("My Test Message");
match Something::get() {
None => {
print(Error::<T>::NoneValue);
Err(Error::<T>::NoneValue)?
}
Some(old) => {
let new = old.checked_add(1).ok_or(
{
print(Error::<T>::StorageOverflow);
Error::<T>::StorageOverflow
})?;
Something::put(new);
Ok(())
},
}
}
使用RUST_LOG环境变量来运行节点二进制文件以打印值。
RUST_LOG=runtime=debug ./target/release/node-template --dev
每次调用runtime函数时,值都会打印在终端或标准输出中。
2020-01-01 tokio-blocking-driver DEBUG runtime My Test Message <-- str implements Printable by default
2020-01-01 tokio-blocking-driver DEBUG runtime Invalid Value <-- the custom string from NoneValue
2020-01-01 tokio-blocking-driver DEBUG runtime DispatchError
2020-01-01 tokio-blocking-driver DEBUG runtime 8
2020-01-01 tokio-blocking-driver DEBUG runtime 0 <-- index value from the Error enum definition
2020-01-01 tokio-blocking-driver DEBUG runtime NoneValue <-- str which holds the name of the ident of the error
重要说明:向runtime添加多个打印函数会导致二进制文件和wasm blob变得更大,而这些增加的调试代码在生产环境中是用不上的。
print
函数
Substrate自带的对于传统用例,Substrate提供了额外的工具来进行Print
调试(或跟踪)。 You can use the print
function to log the status of the runtime execution.
use sp_runtime::print;
// --snip--
pub fn do_something(origin) -> DispatchResult {
print("Execute do_something");
let who = ensure_signed(origin)?;
let my_val: u32 = 777;
Something::put(my_val);
print("After storing my_val");
Self::deposit_event(RawEvent::SomethingStored(my_val, who));
Ok(())
}
// --snip--
可使用RUST_LOG
环境变量启动区块链,以查看打印日志。
RUST_LOG=runtime=debug ./target/release/node-template --dev
如果触发了错误,这些值将打印在终端或标准输出中。
2020-01-01 00:00:00 tokio-blocking-driver DEBUG runtime Execute do_something
2020-01-01 00:00:00 tokio-blocking-driver DEBUG runtime After storing my_val
If Std
传统的print
函数允许您打印并实现Printable
trait。 但是,在某些传统用例下,您可能想要做的不仅仅是打印,或者不想仅仅出于调试目的而去麻烦地使用Substrate的一些traits。 The if_std!
macro is useful for this situation.
使用此宏时要注意,仅当真正运行的是runtime的native版本时,宏内部代码才会执行。
use sp_std::if_std; // Import into scope the if_std! macro.
println!
语句应放置在if_std
宏内。
decl_module! {
// --snip--
pub fn do_something(origin) -> DispatchResult {
let who = ensure_signed(origin)?;
let my_val: u32 = 777;
Something::put(my_val);
if_std! {
// This code is only being compiled and executed when the `std` feature is enabled.
println!("Hello native world!");
println!("My value is: {:#?}", my_val);
println!("The caller account is: {:#?}", who);
}
Self::deposit_event(RawEvent::SomethingStored(my_val, who));
Ok(())
}
// --snip--
}
每次调用runtime函数时,值都会打印在终端或标准输出中。
$ 2020-01-01 00:00:00 Substrate Node
2020-01-01 00:00:00 version x.y.z-x86_64-linux-gnu
2020-01-01 00:00:00 by Anonymous, 2017, 2020
2020-01-01 00:00:00 Chain specification: Development
2020-01-01 00:00:00 Node name: my-node-007
2020-01-01 00:00:00 Roles: AUTHORITY
2020-01-01 00:00:00 Imported 999 (0x3d7a…ab6e)
# --snip--
-> Hello native world!
-> My value is: 777
-> The caller account is: d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d (5GrwvaEF...)
# --snip--
2020-01-01 00:00:00 Imported 1000 (0x3d7a…ab6e)