Skip to main content

Logging

The logging example demonstrates how to log for the purpose of debugging.

Open in Gitpod

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 no 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.

Run the Example

First go through the Setup process to get your development environment configured, then clone the v0.2.1 tag of soroban-examples repository:

git clone -b v0.2.1 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

Cargo.toml
[profile.release-with-logs]
inherits = "release"
debug-assertions = true
logging/src/lib.rs
#![no_std]
use soroban_sdk::{contractimpl, log, Env, Symbol};

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/v0.2.1/logging

How it Works

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.

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)
caution

The values outputted are currently relatively limited. While primitive values like u32, u64, bool, and Symbols 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.

logging/src/test.rs
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!("Dev"));

let logs = env.logger().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!("Dev"));

Logs are available in tests via the environment.

let logs = env.logger().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 cargo build command.

Without Logs

To build the contract without logs, use the --release option.

cargo build --target wasm32-unknown-unknown --release

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.

cargo build --target wasm32-unknown-unknown --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 soroban-cli installed, you can invoke contract functions in the using it.

soroban invoke \
--wasm ../target/wasm32-unknown-unknown/release-with-logs/soroban_logging_contract.wasm \
--id 1 \
--fn hello \
--arg friend

The following output should occur using the code above.

null
#0: debug: Hello Symbol(friend)