Skip to main content

Logging

Beginner
Tutorial

Overview

The canister logging feature is designed to provide developers with insight into their canister's behavior and assist with scenarios where their canister traps. This feature supports log messages for:

  • Heartbeats.

  • Timers.

  • Pre/post upgrade and canister_init.

  • Update calls.

  • Queries, only if called in replicated mode.

A canister's controller is able to retrieve the canister's logs, even when executions trap, using dfx canister logs.

Non-replicated calls are not currently supported for logging.

Log visibility

By default, a canister's logs are only visible to the controllers of the canister. A canister's logs can be made public with the --log-visibility flag:

dfx canister update-settings CANISTER_ID --log-visibility public

Log viewer allow lists

Canister log allow lists enable principals that are not controllers of the canister to view the logs without the logs being public. Log allow lists are supported in dfx versions 0.24.2 and newer.

To add a principal to the canister's log allow list, update the canister's settings with the --set-log-viewer or --add-log-viewer flags:

dfx canister update-settings CANISTER_ID --set-log-viewer PRINCIPAL_ID // Set a single principal as a log viewer
dfx canister update-settings CANISTER_ID --add-log-viewer PRINCIPAL_ID // Add a principal to a list of log viewers

You can also configure a principal as a log viewer when you create a canister with:

dfx canister create CANISTER_NAME --log-viewer PRINCIPAL_ID

Or, you can set the canisters[].initialization_values.log_visibility.allowed_viewers configuration setting in your project's dfx.json file.

To remove a principal from the canister's log allow list, use the --remove-log-viewer flag:

dfx canister update-settings CANISTER_ID --remove-log-viewer PRINCIPAL_ID

Using dfx

A canister's logs can be viewed using the dfx canister logs command.

As an example, consider a Rust canister with the following code:

use std::cell::RefCell;

use candid::{Nat, Principal};
use ic_cdk::{
export_candid, println,
};

thread_local! {
// create a cell that holds a counter
static COUNTER: std::cell::RefCell<u32> = RefCell::default();
}


#[ic_cdk::update]
fn produce_logs() {
// print the counter value
COUNTER.with_borrow_mut(|counter| {
println!("counter: {}", *counter);
*counter += 1;
});
}

export_candid!();

This simple canister provides an incremental counter method that includes a print function that will return output to the command line and the canister's logs.

Note that this example uses update calls, as non-replicated query calls do not currently support logging.

You can call the canister's method and view the associated logged output for each call. For example, if this canister is deployed as logs_backend, you can call the canister's produce_logs method a few times to increment the counter and get the following command line output:

dfx canister call logs_backend produce_logs
2024-06-04 21:14:25.882276 UTC: [Canister br5f7-7uaaa-aaaaa-qaaca-cai] counter: 0
()
dfx canister call logs_backend produce_logs
2024-06-04 21:18:18.094003 UTC: [Canister br5f7-7uaaa-aaaaa-qaaca-cai] counter: 1
()
dfx canister call logs_backend produce_logs
2024-06-04 21:18:46.036022 UTC: [Canister br5f7-7uaaa-aaaaa-qaaca-cai] counter: 2
()

Then, you can call the canister's logs, which in this case will return the same output that was logged to the command line:

dfx canister logs logs_backend
[0. 2024-06-04T21:14:25.882276Z]: counter: 0
[1. 2024-06-04T21:18:18.094003Z]: counter: 1
[2. 2024-06-04T21:18:46.036022Z]: counter: 2