Debugging is a necessity in all walks of software development, and blockchain is no exception. Most of the same tools used for general purpose Rust debugging also apply to Substrate.
Logging utilities
You can use Rust's logging API to debug your runtimes. This comes with a number of macros, including debug and info.
For example, after updating your pallet's Cargo.toml file with the log crate just use log::info! to log to your console:
pubfndo_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 trait
The Printable trait is meant to be a way to print from the runtime in no_std and in std. The print function works with any type that implements the Printable trait. Substrate implements this trait for some types (u8, u32, u64, usize, &[u8], &str) by default. You can also implement it for your own custom types. Here is an example of implementing it for a pallet's Error type using the node-template as the example codebase.
use sp_runtime::traits::Printable;use sp_runtime::print;
#[frame_support::pallet]pubmod pallet {// The pallet's errors #[pallet::error]pubenumError<T> {/// Value was NoneNoneValue,/// Value reached maximum and cannot be incremented furtherStorageOverflow, }impl<T:Config> PrintableforError<T> {fnprint(&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 errorpubfncause_error(origin) -> dispatch::DispatchResult {// Check it was signed and get the signer. See also: ensure_root and ensure_nonelet _who =ensure_signed(origin)?;print!("My Test Message");matchSomething::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(()) }, }}
Run the node binary with the RUST_LOG environment variable to print the values.
The values are printed in the terminal or the standard output every time that the runtime function gets called.
2020-01-01 tokio-blocking-driver DEBUG runtime MyTestMessage<--str implements Printable by default2020-01-01 tokio-blocking-driver DEBUG runtime InvalidValue<-- the custom string from NoneValue2020-01-01 tokio-blocking-driver DEBUG runtime DispatchError2020-01-01 tokio-blocking-driver DEBUG runtime 82020-01-01 tokio-blocking-driver DEBUG runtime 0<-- index value from the Errorenum definition2020-01-01 tokio-blocking-driver DEBUG runtime NoneValue<--str which holds the name of the ident of the error
Keep in mind that adding print functions to the runtime increases the size of the Rust and Wasm binaries with debug code not needed in production.
Substrate's own Print function
For legacy use cases, Substrate provides extra tools for Print debugging (or tracing). You can use the print function to log the status of the runtime execution.
use sp_runtime::print;// --snip--pubfndo_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--
Start the chain using the RUST_LOG environment variable to see the print logs.
The legacy print function allows you to print and have an implementation of the Printable trait. However, in some legacy cases you may want to do more than print, or not bother with Substrate-specific traits just for debugging purposes. The if_std! macro is useful for this situation.
One caveat of using this macro is that the code inside will only execute when you are actually running the native version of the runtime.
use sp_std::if_std; // Import into scope the if_std! macro.
The println! statement should be inside of the if_std macro.
#[pallet::call]impl<T:Config<I>, I: 'static> Pallet<T, I> {// --snip--pubfndo_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--}
The values are printed in the terminal or the standard output every time that the runtime function gets called.