Logging
The logging example demonstrates how to log for the purpose of debugging.
Logs in contracts are only visible in tests, or when executing contracts using stellar-cli
. Logs are only compiled into the contract if the debug-assertions
Rust compiler option is enabled.
Logs are not a substitute for step-through debugging. Rust tests for Soroban can be step-through debugged in your Rust-enabled IDE. See testing for more details.
Logs are not accessible by dapps and other applications. See the events example for how to produce structured events.
Run the Example
First go through the Setup process to get your development environment configured, then clone the v21.6.0
tag of soroban-examples
repository:
git clone -b v21.6.0 https://github.com/stellar/soroban-examples
Or, skip the development environment setup and open this example in Gitpod.
To run the tests for the example, navigate to the logging
directory, and use cargo test
.
cd logging
cargo test -- --nocapture
You should see the output:
running 1 test
Hello Symbol(Dev)
test test::test ... ok
Code
[profile.release-with-logs]
inherits = "release"
debug-assertions = true
#![no_std]
use soroban_sdk::{contractimpl, log, Env, Symbol};
#[contract]
pub struct Contract;
#[contractimpl]
impl Contract {
pub fn hello(env: Env, value: Symbol) {
log!(&env, "Hello {}", value);
}
}
Ref: https://github.com/stellar/soroban-examples/tree/v21.6.0/logging
How it Works
The log!
macro logs a string. Any logs that occur during execution are outputted to stdout in stellar-cli
and available for tests to assert on or print.
Logs are only outputted if the contract is built with the debug-assertions
compiler option enabled. This makes them efficient to leave in code permanently since a regular release
build will omit them.
Logs are only recorded in Soroban environments that have logging enabled. The only Soroban environments where logging is enabled is in Rust tests, and in the stellar-cli
.
Open the files above to follow along.
Cargo.toml
Profile
Logs are only outputted if the contract is built with the debug-assertions
compiler option enabled.
The test
profile that is activated when running cargo test
has debug-assertions
enabled, so when running tests logs are enabled by default.
A new release-with-logs
profile is added to Cargo.toml
that inherits from the release
profile, and enables debug-assertions
. It can be used to build a .wasm
file that has logs enabled.
[profile.release-with-logs]
inherits = "release"
debug-assertions = true
To build without logs use the --release
or --profile release
option.
To build with logs use the --profile release-with-logs
option.
Using the log!
Macro
The log!
macro builds a string from the format string, and a list of arguments. Arguments are substituted wherever the {}
value appears in the format string.
log!(&env, "Hello {}", value);
The above log will render as follows if value
is a Symbol
containing "Dev"
.
Hello Symbol(Dev)
The values outputted are currently relatively limited. While primitive values like u32
, u64
, bool
, and Symbol
s will render clearly in the log output, Bytes
, Vec
, Map
, and custom types will render only their handle number. Logging capabilities are in early development.
Tests
Open the logging/src/test.rs
file to follow along.
extern crate std;
#[test]
fn test() {
let env = Env::default();
let contract_id = env.register_contract(None, Contract);
let client = ContractClient::new(&env, &contract_id);
client.hello(&symbol_short!("Dev"));
let logs = env.logs().all();
assert_eq!(logs, std::vec!["Hello Symbol(Dev)"]);
std::println!("{}", logs.join("\n"));
}
The std
crate, which contains the Rust standard library, is imported so that the test can use the std::vec!
and std::println!
macros. Since contracts are required to use #![no_std]
, tests in contracts must manually import std
to use std functionality like printing to stdout.
extern crate std;
In any test the first thing that is always required is an Env
, which is the Soroban environment that the contract will run in.
let env = Env::default();
The contract is registered with the environment using the contract type.
let contract_id = env.register_contract(None, HelloContract);
All public functions within an impl
block that is annotated with the #[contractimpl]
attribute have a corresponding function generated in a generated client type. The client type will be named the same as the contract type with Client
appended. For example, in our contract the contract type is HelloContract
, and the client is named HelloContractClient
.
let client = HelloContractClient::new(&env, &contract_id);
let words = client.hello(&symbol_short!("Dev"));
Logs are available in tests via the environment.
let logs = env.logs().all();
They can asserted on like any other value.
assert_eq!(logs, std::vec!["Hello Symbol(Dev)"]);
They can be printed to stdout.
std::println!("{}", logs.join("\n"));
Build the Contract
To build the contract, use the stellar contract build
command.
Without Logs
To build the contract without logs, use the --release
option.
stellar contract build
A .wasm
file should be outputted in the target
directory, in the release
subdirectory:
target/wasm32-unknown-unknown/release/soroban_logging_contract.wasm
With Logs
To build the contract with logs, use the --profile release-with-logs
option.
stellar contract build --profile release-with-logs
A .wasm
file should be outputted in the target
directory, in the release-with-logs
subdirectory:
target/wasm32-unknown-unknown/release-with-logs/soroban_logging_contract.wasm
Run the Contract
If you have stellar-cli
installed, you can invoke contract functions in the using it. Specify the -v
option to enable verbose logs.
- macOS/Linux
- Windows (PowerShell)
stellar -v contract invoke \
--wasm target/wasm32-unknown-unknown/release-with-logs/soroban_logging_contract.wasm \
--id 1 \
-- \
hello \
--value friend
stellar -v contract invoke `
--wasm target/wasm32-unknown-unknown/release-with-logs/soroban_logging_contract.wasm `
--id 1 `
-- `
hello `
--value friend
The output should include the following line.
stellar_cli::log::event::contract_log: log="Hello Symbol(me)"