调试

⚠️ Update Notice:

Please read Substrate to Polkadot SDK page first.


调试在所有软件开发领域都是必需的,区块链也不例外。大多数用于通用 Rust 调试的工具也适用于 Substrate。

日志记录实用程序

您可以使用 Rust 的日志记录 API 来调试运行时。这包含许多宏,包括 debuginfo

例如,在使用 log crate 更新 pallet 的 Cargo.toml 文件后,只需使用 log::info! 即可记录到控制台:

pub fn do_something(origin) -> DispatchResult {

	let who = ensure_signed(origin)?;
	let my_val: u32 = 777;

	Something::put(my_val);

	log::info!("called by {:?}", who);

	Self::deposit_event(RawEvent::SomethingStored(my_val, who));
	Ok(())
}

Printable 特性

Printable 特性旨在成为一种在 no_stdstd 中从运行时打印的方法。print 函数适用于实现 Printable 特性 的任何类型。 Substrate 默认情况下会为某些类型 (u8u32u64usize&[u8]&str) 实现此特性。 您也可以为自己的自定义类型实现它。以下是如何使用节点模板作为示例代码库为 pallet 的 Error 类型实现它的示例。

use sp_runtime::traits::Printable;
use sp_runtime::print;
#[frame_support::pallet]
pub mod pallet {
	// pallet 的错误
	#[pallet::error]
	pub enum Error<T> {
		/// 值为 None
		NoneValue,
		/// 值已达到最大值,无法进一步递增
		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(),
			}
		}
	}
}
/// 不接受参数,尝试递增存储值,并可能抛出错误
pub fn cause_error(origin) -> dispatch::DispatchResult {
	// 检查它是否已签名并获取签名者。另请参阅:ensure_root 和 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/solo-template-node --dev

每次调用运行时函数时,都会在终端或标准输出中打印这些值。

2020-01-01 tokio-blocking-driver DEBUG runtime  My Test Message  <-- str 默认实现 Printable
2020-01-01 tokio-blocking-driver DEBUG runtime  Invalid Value    <-- 来自 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                <-- 来自 Error 枚举定义的索引值
2020-01-01 tokio-blocking-driver DEBUG runtime  NoneValue        <-- 包含错误标识符名称的 str

请记住,向运行时添加打印函数会增加 Rust 和 Wasm 二进制文件的大小,其中调试代码在生产环境中不需要。

Substrate 自身的 print 函数

对于旧版用例,Substrate 提供了额外的 Print 调试(或跟踪)工具。您可以使用 print 函数 来记录运行时执行的状态。

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/solo-template-node --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

如果 std

旧版 print 函数允许您打印并实现 Printable 特性。 但是,在某些旧版情况下,您可能希望执行的操作不仅仅是打印,或者只是为了调试目的而无需使用 Substrate 特定的特性。 if_std! 对于这种情况很有用。

使用此宏的一个警告是,其中的代码仅在实际运行运行时的本机版本时才会执行。

use sp_std::if_std; // 将 if_std! 宏导入作用域。

println! 语句应该在 if_std 宏内。

#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
		// --snip--
		pub fn do_something(origin) -> DispatchResult {

			let who = ensure_signed(origin)?;
			let my_val: u32 = 777;

			Something::put(my_val);

			if_std! {
				// 仅在启用 `std` 功能时才编译和执行此代码。
				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--
}

每次调用运行时函数时,都会在终端或标准输出中打印这些值。

$		2020-01-01 00:00:00 Substrate 节点
		2020-01-01 00:00:00   版本 x.y.z-x86_64-linux-gnu
		2020-01-01 00:00:00   作者:Anonymous,2017,2020
		2020-01-01 00:00:00 链规范:开发
		2020-01-01 00:00:00 节点名称:my-node-007
		2020-01-01 00:00:00 角色:AUTHORITY
		2020-01-01 00:00:00 已导入 999 (0x3d7a…ab6e)
		# --snip--
->		Hello native world!
->		My value is: 777
->		The caller account is: d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d (5GrwvaEF...)
		# --snip--
		2020-01-01 00:00:00 已导入 1000 (0x3d7a…ab6e)