title | description | sidebar_custom_props | ||||
---|---|---|---|---|---|---|
Logging |
Debug a smart contract with logs. |
|
The logging example demonstrates how to log for the purpose of debugging.
[][oigp]
[oigp]: https://gitpod.io/#https://github.com/stellar/soroban-examples/tree/v20.0.0
Logs in contracts are only visible in tests, or when executing contracts using
soroban-cli
. Logs are only compiled into the contract if the
debug-assertions
Rust compiler option is enabled.
:::tip
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.
:::
:::caution
Logs are not accessible by dapps and other applications. See the events example for how to produce structured events.
:::
First go through the Setup process to get your development environment
configured, then clone the v20.0.0
tag of soroban-examples
repository:
git clone -b v20.0.0 https://github.com/stellar/soroban-examples
Or, skip the development environment setup and open this example in [Gitpod][oigp].
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
[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/v20.0.0/logging
The log!
macro logs a string. Any logs that occur during execution are
outputted to stdout in soroban-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
soroban-cli
.
Open the files above to follow along.
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.
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)
:::caution
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.
:::
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"));
To build the contract, use the soroban contract build
command.
To build the contract without logs, use the --release
option.
soroban 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
To build the contract with logs, use the --profile release-with-logs
option.
soroban 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
If you have soroban-cli
installed, you can invoke contract functions in the
using it. Specify the -v
option to enable verbose logs.
soroban -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.
soroban_cli::log::event::contract_log: log="Hello Symbol(me)"