From a2765a54871004711d2543c2ec9d6d1c62413cb6 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 10 Jun 2026 17:17:34 +0200 Subject: [PATCH 01/10] chore: migrate motoko/canister_logs to icp-cli Replace dfx tooling with icp-cli: add icp.yaml, update mops.toml to moc 1.9.0 and core 2.5.0, move src/Main.mo to backend/app.mo, replace inline actor("aaaaa-aa") raw_rand with mo:ic ic.raw_rand(), rewrite Makefile tests for icp canister call/logs, add CI workflow, and update README. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/canister_logs.yml | 28 ++++ .../.devcontainer/devcontainer.json | 20 --- motoko/canister_logs/BUILD.md | 113 ------------- motoko/canister_logs/Makefile | 108 +++++------- motoko/canister_logs/README.md | 154 ++---------------- .../{src/Main.mo => backend/app.mo} | 4 +- motoko/canister_logs/dfx.json | 14 -- motoko/canister_logs/icp.yaml | 4 + motoko/canister_logs/mops.toml | 10 +- 9 files changed, 102 insertions(+), 353 deletions(-) create mode 100644 .github/workflows/canister_logs.yml delete mode 100644 motoko/canister_logs/.devcontainer/devcontainer.json delete mode 100644 motoko/canister_logs/BUILD.md rename motoko/canister_logs/{src/Main.mo => backend/app.mo} (91%) delete mode 100644 motoko/canister_logs/dfx.json create mode 100644 motoko/canister_logs/icp.yaml diff --git a/.github/workflows/canister_logs.yml b/.github/workflows/canister_logs.yml new file mode 100644 index 000000000..e482591e2 --- /dev/null +++ b/.github/workflows/canister_logs.yml @@ -0,0 +1,28 @@ +name: canister_logs + +on: + push: + branches: [master] + pull_request: + paths: + - motoko/canister_logs/** + - .github/workflows/canister_logs.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + motoko-canister_logs: + runs-on: ubuntu-24.04 + container: ghcr.io/dfinity/icp-dev-env-motoko:0.3.1 + env: + ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Deploy and test + working-directory: motoko/canister_logs + run: | + icp network start -d + icp deploy + make test diff --git a/motoko/canister_logs/.devcontainer/devcontainer.json b/motoko/canister_logs/.devcontainer/devcontainer.json deleted file mode 100644 index ebb0b8bcc..000000000 --- a/motoko/canister_logs/.devcontainer/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "ICP Dev Environment", - "image": "ghcr.io/dfinity/icp-dev-env-slim:22", - "forwardPorts": [4943, 5173], - "portsAttributes": { - "4943": { - "label": "dfx", - "onAutoForward": "ignore" - }, - "5173": { - "label": "vite", - "onAutoForward": "openBrowser" - } - }, - "customizations": { - "vscode": { - "extensions": ["dfinity-foundation.vscode-motoko"] - } - } -} diff --git a/motoko/canister_logs/BUILD.md b/motoko/canister_logs/BUILD.md deleted file mode 100644 index 24cfcb754..000000000 --- a/motoko/canister_logs/BUILD.md +++ /dev/null @@ -1,113 +0,0 @@ -# Continue building locally - -Projects deployed through ICP Ninja are temporary; they will only be live for 20 minutes before they are removed. The command-line tool `dfx` can be used to continue building your ICP Ninja project locally and deploy it to the mainnet. - -To migrate your ICP Ninja project off of the web browser and develop it locally, follow these steps. - -### 1. Install developer tools. - -You can install the developer tools natively or use Dev Containers. - -#### Option 1: Natively install developer tools - -> Installing `dfx` natively is currently only supported on macOS and Linux systems. On Windows, it is recommended to use the Dev Containers option. - -1. Install `dfx` with the following command: - -``` - -sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)" - -``` - -> On Apple Silicon (e.g., Apple M1 chip), make sure you have Rosetta installed (`softwareupdate --install-rosetta`). - -2. [Install NodeJS](https://nodejs.org/en/download/package-manager). - -3. For Rust projects, you will also need to: - -- Install [Rust](https://doc.rust-lang.org/cargo/getting-started/installation.html#install-rust-and-cargo): `curl https://sh.rustup.rs -sSf | sh` - -- Install [candid-extractor](https://crates.io/crates/candid-extractor): `cargo install candid-extractor` - -4. For Motoko projects, you will also need to: - -- Install the Motoko package manager [Mops](https://docs.mops.one/quick-start#2-install-mops-cli): `npm i -g ic-mops` - -Lastly, navigate into your project's directory that you downloaded from ICP Ninja. - -#### Option 2: Dev Containers - -Continue building your projects locally by installing the [Dev Container extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) for VS Code and [Docker](https://docs.docker.com/engine/install/). - -Make sure Docker is running, then navigate into your project's directory that you downloaded from ICP Ninja and start the Dev Container by selecting `Dev-Containers: Reopen in Container` in VS Code's command palette (F1 or Ctrl/Cmd+Shift+P). - -> Note that local development ports (e.g. the ports used by `dfx` or `vite`) are forwarded from the Dev Container to your local machine. In the VS code terminal, use Ctrl/Cmd+Click on the displayed local URLs to open them in your browser. To view the current port mappings, click the "Ports" tab in the VS Code terminal window. - -### 2. Start the local development environment. - -``` -dfx start --background -``` - -### 3. Create a local developer identity. - -To manage your project's canisters, it is recommended that you create a local [developer identity](https://internetcomputer.org/docs/building-apps/getting-started/identities) rather than use the `dfx` default identity that is not stored securely. - -To create a new identity, run the commands: - -``` - -dfx identity new IDENTITY_NAME - -dfx identity use IDENTITY_NAME - -``` - -Replace `IDENTITY_NAME` with your preferred identity name. The first command `dfx start --background` starts the local `dfx` processes, then `dfx identity new` will create a new identity and return your identity's seed phase. Be sure to save this in a safe, secure location. - -The third command `dfx identity use` will tell `dfx` to use your new identity as the active identity. Any canister smart contracts created after running `dfx identity use` will be owned and controlled by the active identity. - -Your identity will have a principal ID associated with it. Principal IDs are used to identify different entities on ICP, such as users and canisters. - -[Learn more about ICP developer identities](https://internetcomputer.org/docs/building-apps/getting-started/identities). - -### 4. Deploy the project locally. - -Deploy your project to your local developer environment with: - -``` -npm install -dfx deploy - -``` - -Your project will be hosted on your local machine. The local canister URLs for your project will be shown in the terminal window as output of the `dfx deploy` command. You can open these URLs in your web browser to view the local instance of your project. - -### 5. Obtain cycles. - -To deploy your project to the mainnet for long-term public accessibility, first you will need [cycles](https://internetcomputer.org/docs/building-apps/getting-started/tokens-and-cycles). Cycles are used to pay for the resources your project uses on the mainnet, such as storage and compute. - -> This cost model is known as ICP's [reverse gas model](https://internetcomputer.org/docs/building-apps/essentials/gas-cost), where developers pay for their project's gas fees rather than users pay for their own gas fees. This model provides an enhanced end user experience since they do not need to hold tokens or sign transactions when using a dapp deployed on ICP. - -> Learn how much a project may cost by using the [pricing calculator](https://internetcomputer.org/docs/building-apps/essentials/cost-estimations-and-examples). - -Cycles can be obtained through [converting ICP tokens into cycles using `dfx`](https://internetcomputer.org/docs/building-apps/developer-tools/dfx/dfx-cycles#dfx-cycles-convert). - -### 6. Deploy to the mainnet. - -Once you have cycles, run the command: - -``` - -dfx deploy --network ic - -``` - -After your project has been deployed to the mainnet, it will continuously require cycles to pay for the resources it uses. You will need to [top up](https://internetcomputer.org/docs/building-apps/canister-management/topping-up) your project's canisters or set up automatic cycles management through a service such as [CycleOps](https://cycleops.dev/). - -> If your project's canisters run out of cycles, they will be removed from the network. - -## Additional examples - -Additional code examples and sample applications can be found in the [DFINITY examples repo](https://github.com/dfinity/examples). diff --git a/motoko/canister_logs/Makefile b/motoko/canister_logs/Makefile index bb1053d22..93bf71723 100644 --- a/motoko/canister_logs/Makefile +++ b/motoko/canister_logs/Makefile @@ -1,66 +1,46 @@ -.PHONY: all -all: build - -.PHONY: deploy -.SILENT: deploy -install: - dfx canister deploy CanisterLogs - .PHONY: test -.SILENT: test -test: deploy - # Test print via update call. - dfx canister call CanisterLogs print 'print via update' - dfx canister logs CanisterLogs \ - | grep 'print via update' && echo 'PASS' - - # Test print via replicated query call. - dfx canister call --update CanisterLogs print_query 'print via replicated query' - dfx canister logs CanisterLogs \ - | grep 'print via replicated query' && echo 'PASS' - - # Test print via non-replicated query call should NOT record the message. - dfx canister call --query CanisterLogs print_query 'print via non-replicated query' - ! dfx canister logs CanisterLogs \ - | grep 'print via non-replicated query' && echo 'PASS' - - # Test trapped call is recorded in logs. - # Ignore failed dfx command output (so the test continues) and check the logs to contain the message. - - dfx canister call CanisterLogs trap 'trap via update' - dfx canister logs CanisterLogs \ - | grep 'trap via update' && echo 'PASS' - - # Test trap via replicated query call. - # Ignore failed dfx command output (so the test continues) and check the logs to contain the message. - - dfx canister call --update CanisterLogs trap_query 'trap via replicated query' - dfx canister logs CanisterLogs \ - | grep 'trap via replicated query' && echo 'PASS' - - # Test trap via non-replicated query call should NOT record the message. - # Ignore failed dfx command output (so the test continues) and check the logs to contain the message. - - dfx canister call --query CanisterLogs trap_query 'trap via non-replicated query' - ! dfx canister logs CanisterLogs \ - | grep 'trap via non-replicated query' && echo 'PASS' - - # Test call to fail with memory out of bounds. - # Ignore failed dfx command output (so the test continues) and check the logs to contain the message. - - dfx canister call CanisterLogs memory_oob - dfx canister logs CanisterLogs \ - | grep 'Region error: range out of bounds' && echo 'PASS' - - # Test timer trap. - # The timer is setup to trap every 5 seconds, so this test has to be called - # at least 5 seconds after the start to record the trap log message. - sleep 5 - dfx canister logs CanisterLogs \ - | grep 'timer trap' && echo 'PASS' - - # Test raw_rand. - dfx canister call CanisterLogs raw_rand - dfx canister logs CanisterLogs \ - | grep 'ic.raw_rand() call succeeded' && echo 'PASS' -.PHONY: clean -.SILENT: clean -clean: - rm -fr .dfx +test: + @echo "[1/9] print via update call is recorded in logs" + @icp canister call backend print '("print via update")' && \ + icp canister logs backend | grep -q 'print via update' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "[2/9] print_query via replicated update call is recorded in logs" + @icp canister call backend print_query '("print via replicated query")' && \ + icp canister logs backend | grep -q 'print via replicated query' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "[3/9] trap via update call is recorded in logs" + @-icp canister call backend trap '("trap via update")' + @icp canister logs backend | grep -q 'trap via update' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "[4/9] trap logs 'right before trap' message before trapping" + @icp canister logs backend | grep -q 'right before trap' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "[5/9] trap_query via update call is recorded in logs" + @-icp canister call backend trap_query '("trap via replicated query")' + @icp canister logs backend | grep -q 'trap via replicated query' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "[6/9] memory_oob trap is recorded in logs" + @-icp canister call backend memory_oob '()' + @icp canister logs backend | grep -q 'Region error: range out of bounds' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "[7/9] timer trap is recorded in logs (waiting up to 10s)" + @sleep 10 && \ + icp canister logs backend | grep -q 'timer trap' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "[8/9] raw_rand returns a blob" + @result=$$(icp canister call backend raw_rand '()') && \ + echo "$$result" && \ + echo "$$result" | grep -q 'blob' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "[9/9] raw_rand success is recorded in logs" + @icp canister logs backend | grep -q 'ic.raw_rand() call succeeded' && \ + echo "PASS" || (echo "FAIL" && exit 1) diff --git a/motoko/canister_logs/README.md b/motoko/canister_logs/README.md index fe7c7e67d..561cebbe7 100644 --- a/motoko/canister_logs/README.md +++ b/motoko/canister_logs/README.md @@ -1,151 +1,31 @@ # Canister logs -This sample project demonstrates a basic logging and error handling system for a canister deployed on the Internet Computer. The `canister_logs` project showcases how to utilize logging for debugging and monitoring canister operations. It also demonstrates the use of timers and error handling through traps. +[View this sample's code on GitHub](https://github.com/dfinity/examples/tree/master/motoko/canister_logs) -The `canister_logs` canister is designed to periodically log messages and simulate errors using traps. It provides methods to print messages, trigger traps, and handle memory out-of-bounds errors. The project includes a script to continuously poll and display logs, making it easier to monitor canister activity in real-time. +## Overview -## Prerequisites +This example demonstrates canister logging and error handling on the Internet Computer. It shows how `Debug.print` messages and trap messages are recorded in the canister log, covering update calls, replicated query calls, timer-triggered traps, memory out-of-bounds errors, and management canister calls like `raw_rand`. -- [x] Install the [IC SDK](https://internetcomputer.org/docs/current/developer-docs/getting-started/install). For local testing, `dfx >= 0.22.0` is required. -- [x] Clone the example dapp project: `git clone https://github.com/dfinity/examples` +## Build and deploy from the command line -## Deploying from ICP Ninja +### Prerequisites +- Node.js +- icp-cli: `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm` -When viewing this project in ICP Ninja, you can deploy it directly to the mainnet for free by clicking "Run" in the upper right corner. Open this project in ICP Ninja: - -[![](https://icp.ninja/assets/open.svg)](https://icp.ninja/i?g=https://github.com/dfinity/examples/motoko/canister_logs) - -## Build and deploy from the command-line - -### 1. [Download and install the IC SDK.](https://internetcomputer.org/docs/building-apps/getting-started/install) - -### 2. Download your project from ICP Ninja using the 'Download files' button on the upper left corner, or [clone the GitHub examples repository.](https://github.com/dfinity/examples/) - -### 3. Navigate into the project's directory. - -### 4. Deploy the project to your local environment: - -You will need to have 3 terminal windows: -- Terminal A: Running a `dfx` instance and separating its output from anything else. -- Terminal B: Deploying a canister and seeing its output. -- Terminal C: Reading logs interactively. - -```shell -# Terminal A -- for running DFX and separating its output from anything else. -cd examples/motoko/canister_logs - -# Terminal B -- for deploying the canister and calling its methods. -cd examples/motoko/canister_logs - -# Terminal C -- for polling logs. +### Install +```bash +git clone https://github.com/dfinity/examples cd examples/motoko/canister_logs ``` -```shell -# Terminal B -dfx deploy -``` - -### Step 5: Check canister logs - -Expect to see logs from timer traps. - -```shell -# Terminal B -$ dfx canister logs CanisterLogs -[0. 2024-05-23T08:32:26.203980235Z]: right before timer trap -[1. 2024-05-23T08:32:26.203980235Z]: [TRAP]: timer trap -[2. 2024-05-23T08:32:31.836721763Z]: right before timer trap -[3. 2024-05-23T08:32:31.836721763Z]: [TRAP]: timer trap -``` - -### Step 6: Call `print` method and check the logs - -```shell -# Terminal B -$ dfx canister call CanisterLogs print hi -() - -# Expect to see new log entry. -$ dfx canister logs CanisterLogs -... -[8. 2024-05-23T08:32:46.598972616Z]: right before timer trap -[9. 2024-05-23T08:32:46.598972616Z]: [TRAP]: timer trap -[10. 2024-05-23T08:32:48.713755238Z]: hi -[11. 2024-05-23T08:32:51.623988313Z]: right before timer trap -[12. 2024-05-23T08:32:51.623988313Z]: [TRAP]: timer trap -... -``` - -### Step 7: Start constantly polling logs - -In order not to call `dfx canister logs CanisterLogs` after every canister call in a separate terminal window/pane C start a script that will constantly poll logs: - -```shell -# Terminal C -$ ./poll_logs.sh -... -[8. 2024-05-23T08:32:46.598972616Z]: right before timer trap -[9. 2024-05-23T08:32:46.598972616Z]: [TRAP]: timer trap -[10. 2024-05-23T08:32:48.713755238Z]: hi -[11. 2024-05-23T08:32:51.623988313Z]: right before timer trap -[12. 2024-05-23T08:32:51.623988313Z]: [TRAP]: timer trap -... -``` - -### Step 8: Call `print`, `trap` and other canister methods - -```shell -# Terminal B -$ dfx canister call CanisterLogs print hi! -() - -$ dfx canister call CanisterLogs print hello! -() - -$ dfx canister call CanisterLogs print yey! -() - -$ dfx canister call CanisterLogs trap oops! -Error: Failed update call. -Caused by: Failed update call. - The replica returned a rejection error: reject code CanisterError, reject message Canister bkyz2-fmaaa-aaaaa-qaaaq-cai trapped explicitly: oops!, error code None - -$ dfx canister call CanisterLogs memory_oob -Error: Failed update call. -Caused by: Failed update call. - The replica returned a rejection error: reject code CanisterError, reject message Canister bkyz2-fmaaa-aaaaa-qaaaq-cai trapped explicitly: Region error: range out of bounds, error code None - -``` - -Observe recorded logs that might look similar to this: - -```shell -# Terminal C -... -[19. 2024-05-23T08:33:11.319493785Z]: right before timer trap -[20. 2024-05-23T08:33:11.319493785Z]: [TRAP]: timer trap -[21. 2024-05-23T08:33:14.229855179Z]: hi! -[22. 2024-05-23T08:33:16.413512126Z]: right before timer trap -[23. 2024-05-23T08:33:16.413512126Z]: [TRAP]: timer trap -[24. 2024-05-23T08:33:18.622686552Z]: hello! -[25. 2024-05-23T08:33:21.519088681Z]: right before timer trap -[26. 2024-05-23T08:33:21.519088681Z]: [TRAP]: timer trap -[27. 2024-05-23T08:33:22.96101893Z]: yey! -[28. 2024-05-23T08:33:26.601860526Z]: right before timer trap -[29. 2024-05-23T08:33:26.601860526Z]: [TRAP]: timer trap -[30. 2024-05-23T08:33:28.039227914Z]: right before trap -[31. 2024-05-23T08:33:28.039227914Z]: [TRAP]: oops! -[32. 2024-05-23T08:33:31.634215234Z]: right before timer trap -[33. 2024-05-23T08:33:31.634215234Z]: [TRAP]: timer trap -[34. 2024-05-23T08:33:35.96761902Z]: right before memory out of bounds -[35. 2024-05-23T08:33:35.96761902Z]: [TRAP]: Region error: range out of bounds -[36. 2024-05-23T08:33:36.712223153Z]: right before timer trap -[37. 2024-05-23T08:33:36.712223153Z]: [TRAP]: timer trap -... - +### Deploy and test +```bash +icp network start -d +icp deploy +make test +icp network stop ``` ## Security considerations and best practices -If you base your application on this example, it is recommended that you familiarize yourself with and adhere to the [security best practices](https://internetcomputer.org/docs/building-apps/security/overview) for developing on ICP. This example may not implement all the best practices. +Refer to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for information on security and best practices for your ICP dapp. diff --git a/motoko/canister_logs/src/Main.mo b/motoko/canister_logs/backend/app.mo similarity index 91% rename from motoko/canister_logs/src/Main.mo rename to motoko/canister_logs/backend/app.mo index d12e96fa9..f2cc3c6ed 100644 --- a/motoko/canister_logs/src/Main.mo +++ b/motoko/canister_logs/backend/app.mo @@ -4,12 +4,12 @@ import Region "mo:core/Region"; import Runtime "mo:core/Runtime"; import { now } = "mo:core/Time"; import { setTimer; recurringTimer } = "mo:core/Timer"; +import { ic } "mo:ic"; persistent actor CanisterLogs { transient let timerDelaySeconds = 5; transient let second = 1_000_000_000; - transient let ic00_raw_rand = (actor "aaaaa-aa" : actor { raw_rand : () -> async Blob }).raw_rand; private func execute_timer() : async () { Debug.print("right before timer trap"); @@ -52,7 +52,7 @@ persistent actor CanisterLogs { public func raw_rand() : async Blob { Debug.print("pre ic.raw_rand() call"); - let bytes = await ic00_raw_rand(); + let bytes = await ic.raw_rand(); Debug.print("ic.raw_rand() call succeeded"); bytes; }; diff --git a/motoko/canister_logs/dfx.json b/motoko/canister_logs/dfx.json deleted file mode 100644 index 47b4b7031..000000000 --- a/motoko/canister_logs/dfx.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "version": 1, - "canisters": { - "CanisterLogs": { - "type": "motoko", - "main": "src/Main.mo" - } - }, - "defaults": { - "build": { - "packtool": "mops sources" - } - } -} \ No newline at end of file diff --git a/motoko/canister_logs/icp.yaml b/motoko/canister_logs/icp.yaml new file mode 100644 index 000000000..fb741fade --- /dev/null +++ b/motoko/canister_logs/icp.yaml @@ -0,0 +1,4 @@ +canisters: + - name: backend + recipe: + type: "@dfinity/motoko@v5.0.0" diff --git a/motoko/canister_logs/mops.toml b/motoko/canister_logs/mops.toml index ddb2e07c5..7902c42eb 100644 --- a/motoko/canister_logs/mops.toml +++ b/motoko/canister_logs/mops.toml @@ -1,11 +1,15 @@ [toolchain] -moc = "1.5.1" +moc = "1.9.0" [dependencies] -core = "2.4.0" +core = "2.5.0" +ic = "4.0.0" [moc] # M0236: use context dot notation # M0237: redundant explicit implicit arguments # M0223: redundant type instantiation -args = ["-W=M0236,M0237,M0223"] +args = ["--default-persistent-actors", "-W=M0236,M0237,M0223"] + +[canisters.backend] +main = "backend/app.mo" From 2b6ba96e7f28814f003e358ec4d86b7ce45c0dc3 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 10 Jun 2026 18:47:30 +0200 Subject: [PATCH 02/10] fix: replace @- with 2>/dev/null || true in canister_logs Makefile; standardize test labels Co-Authored-By: Claude Sonnet 4.6 --- motoko/canister_logs/Makefile | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/motoko/canister_logs/Makefile b/motoko/canister_logs/Makefile index 93bf71723..20e273b52 100644 --- a/motoko/canister_logs/Makefile +++ b/motoko/canister_logs/Makefile @@ -1,46 +1,46 @@ .PHONY: test test: - @echo "[1/9] print via update call is recorded in logs" + @echo "=== Test 1/9: print via update call is recorded in logs ===" @icp canister call backend print '("print via update")' && \ icp canister logs backend | grep -q 'print via update' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "[2/9] print_query via replicated update call is recorded in logs" + @echo "=== Test 2/9: print_query via replicated update call is recorded in logs ===" @icp canister call backend print_query '("print via replicated query")' && \ icp canister logs backend | grep -q 'print via replicated query' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "[3/9] trap via update call is recorded in logs" - @-icp canister call backend trap '("trap via update")' + @echo "=== Test 3/9: trap via update call is recorded in logs ===" + @icp canister call backend trap '("trap via update")' 2>/dev/null || true @icp canister logs backend | grep -q 'trap via update' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "[4/9] trap logs 'right before trap' message before trapping" + @echo "=== Test 4/9: trap logs 'right before trap' message before trapping ===" @icp canister logs backend | grep -q 'right before trap' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "[5/9] trap_query via update call is recorded in logs" - @-icp canister call backend trap_query '("trap via replicated query")' + @echo "=== Test 5/9: trap_query via update call is recorded in logs ===" + @icp canister call backend trap_query '("trap via replicated query")' 2>/dev/null || true @icp canister logs backend | grep -q 'trap via replicated query' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "[6/9] memory_oob trap is recorded in logs" - @-icp canister call backend memory_oob '()' + @echo "=== Test 6/9: memory_oob trap is recorded in logs ===" + @icp canister call backend memory_oob '()' 2>/dev/null || true @icp canister logs backend | grep -q 'Region error: range out of bounds' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "[7/9] timer trap is recorded in logs (waiting up to 10s)" + @echo "=== Test 7/9: timer trap is recorded in logs (waiting up to 10s) ===" @sleep 10 && \ icp canister logs backend | grep -q 'timer trap' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "[8/9] raw_rand returns a blob" + @echo "=== Test 8/9: raw_rand returns a blob ===" @result=$$(icp canister call backend raw_rand '()') && \ echo "$$result" && \ echo "$$result" | grep -q 'blob' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "[9/9] raw_rand success is recorded in logs" + @echo "=== Test 9/9: raw_rand success is recorded in logs ===" @icp canister logs backend | grep -q 'ic.raw_rand() call succeeded' && \ echo "PASS" || (echo "FAIL" && exit 1) From 6455a8eb7ca734fa5bd19e5c31a4ddd94fd926f6 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 10 Jun 2026 19:00:07 +0200 Subject: [PATCH 03/10] feat: expand to 11 tests covering 'right before' messages; add log format to README Co-Authored-By: Claude Sonnet 4.6 --- motoko/canister_logs/Makefile | 26 +++++++++++++++++--------- motoko/canister_logs/README.md | 28 +++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/motoko/canister_logs/Makefile b/motoko/canister_logs/Makefile index 20e273b52..30015d288 100644 --- a/motoko/canister_logs/Makefile +++ b/motoko/canister_logs/Makefile @@ -1,46 +1,54 @@ .PHONY: test test: - @echo "=== Test 1/9: print via update call is recorded in logs ===" + @echo "=== Test 1/11: print via update call is recorded in logs ===" @icp canister call backend print '("print via update")' && \ icp canister logs backend | grep -q 'print via update' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "=== Test 2/9: print_query via replicated update call is recorded in logs ===" + @echo "=== Test 2/11: print_query via replicated update call is recorded in logs ===" @icp canister call backend print_query '("print via replicated query")' && \ icp canister logs backend | grep -q 'print via replicated query' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "=== Test 3/9: trap via update call is recorded in logs ===" + @echo "=== Test 3/11: trap via update call is recorded in logs ===" @icp canister call backend trap '("trap via update")' 2>/dev/null || true @icp canister logs backend | grep -q 'trap via update' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "=== Test 4/9: trap logs 'right before trap' message before trapping ===" + @echo "=== Test 4/11: trap logs 'right before trap' message before trapping ===" @icp canister logs backend | grep -q 'right before trap' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "=== Test 5/9: trap_query via update call is recorded in logs ===" + @echo "=== Test 5/11: trap_query via update call is recorded in logs ===" @icp canister call backend trap_query '("trap via replicated query")' 2>/dev/null || true @icp canister logs backend | grep -q 'trap via replicated query' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "=== Test 6/9: memory_oob trap is recorded in logs ===" + @echo "=== Test 6/11: trap_query logs 'right before trap_query' before trapping ===" + @icp canister logs backend | grep -q 'right before trap_query' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 7/11: memory_oob trap is recorded in logs ===" @icp canister call backend memory_oob '()' 2>/dev/null || true @icp canister logs backend | grep -q 'Region error: range out of bounds' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "=== Test 7/9: timer trap is recorded in logs (waiting up to 10s) ===" + @echo "=== Test 8/11: memory_oob logs 'right before memory out of bounds' ===" + @icp canister logs backend | grep -q 'right before memory out of bounds' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 9/11: timer trap is recorded in logs (waiting up to 10s) ===" @sleep 10 && \ icp canister logs backend | grep -q 'timer trap' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "=== Test 8/9: raw_rand returns a blob ===" + @echo "=== Test 10/11: raw_rand returns a blob ===" @result=$$(icp canister call backend raw_rand '()') && \ echo "$$result" && \ echo "$$result" | grep -q 'blob' && \ echo "PASS" || (echo "FAIL" && exit 1) - @echo "=== Test 9/9: raw_rand success is recorded in logs ===" + @echo "=== Test 11/11: raw_rand success is recorded in logs ===" @icp canister logs backend | grep -q 'ic.raw_rand() call succeeded' && \ echo "PASS" || (echo "FAIL" && exit 1) diff --git a/motoko/canister_logs/README.md b/motoko/canister_logs/README.md index 561cebbe7..d88d78209 100644 --- a/motoko/canister_logs/README.md +++ b/motoko/canister_logs/README.md @@ -4,7 +4,27 @@ ## Overview -This example demonstrates canister logging and error handling on the Internet Computer. It shows how `Debug.print` messages and trap messages are recorded in the canister log, covering update calls, replicated query calls, timer-triggered traps, memory out-of-bounds errors, and management canister calls like `raw_rand`. +This example demonstrates canister logging on the Internet Computer. Every message written with `Debug.print` and every trap is recorded in the canister's log, which can be retrieved at any time with `icp canister logs`. The example covers: + +- **Update calls** — `Debug.print` output from update methods +- **Replicated query calls** — `Debug.print` output from query methods called as updates +- **Explicit traps** — messages logged before and at the point of a `Runtime.trap` +- **Memory out-of-bounds** — automatically logged trap from accessing an unallocated region +- **Timer-triggered traps** — periodic traps set up at canister install time +- **Management canister calls** — `ic.raw_rand` logging success/failure + +## Log entry format + +Each log entry has a sequence number, a timestamp, and the message: + +``` +[0. 2024-05-23T08:32:26.203980235Z]: right before timer trap +[1. 2024-05-23T08:32:26.203980235Z]: [TRAP]: timer trap +[2. 2024-05-23T08:32:31.836721763Z]: right before timer trap +[3. 2024-05-23T08:32:31.836721763Z]: [TRAP]: timer trap +``` + +`Debug.print` messages appear as plain text. Trap messages are prefixed with `[TRAP]:`. When a function calls `Debug.print` before trapping, both entries appear in sequence — the print message first, then the trap. ## Build and deploy from the command line @@ -26,6 +46,12 @@ make test icp network stop ``` +To inspect the raw log entries at any point: + +```bash +icp canister logs backend +``` + ## Security considerations and best practices Refer to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for information on security and best practices for your ICP dapp. From 9dc4f5d69a2794cc62eb831439a3d9c588b8e774 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 10 Jun 2026 19:14:56 +0200 Subject: [PATCH 04/10] chore: update poll_logs.sh to use icp-cli; mention it in README Co-Authored-By: Claude Sonnet 4.6 --- motoko/canister_logs/README.md | 6 ++++++ motoko/canister_logs/poll_logs.sh | 15 ++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/motoko/canister_logs/README.md b/motoko/canister_logs/README.md index d88d78209..4af2e716c 100644 --- a/motoko/canister_logs/README.md +++ b/motoko/canister_logs/README.md @@ -52,6 +52,12 @@ To inspect the raw log entries at any point: icp canister logs backend ``` +To watch logs stream in real-time while calling methods in a separate terminal: + +```bash +./poll_logs.sh +``` + ## Security considerations and best practices Refer to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for information on security and best practices for your ICP dapp. diff --git a/motoko/canister_logs/poll_logs.sh b/motoko/canister_logs/poll_logs.sh index 71d03f337..de07b41be 100755 --- a/motoko/canister_logs/poll_logs.sh +++ b/motoko/canister_logs/poll_logs.sh @@ -1,25 +1,26 @@ #!/bin/bash -# Function to fetch logs and filter out new lines +# Continuously polls and displays new canister log entries. +# Useful for watching logs stream in real-time while calling canister methods +# in a separate terminal. + +declare -a previous_logs=() + fetch_and_filter_logs() { - # Fetch logs - new_logs=$(dfx canister logs CanisterLogs) + local new_logs + new_logs=$(icp canister logs backend) - # Compare with previous logs to find new ones while IFS= read -r line; do if [[ ! "${previous_logs[*]}" =~ "$line" ]]; then echo "$line" fi done <<< "$new_logs" - # Update previous logs previous_logs=("$new_logs") } -# Initial fetch and filter fetch_and_filter_logs -# Infinite loop to continuously fetch and filter logs while true; do fetch_and_filter_logs sleep 1 From 18a4fb8b453f00289fc35054bb874512a98702e4 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 10 Jun 2026 21:36:40 +0200 Subject: [PATCH 05/10] chore: retrigger CI Co-Authored-By: Claude Sonnet 4.6 From 6cb7ce04df8950b2c1f2bca7cb687d128f34d483 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 10 Jun 2026 21:51:45 +0200 Subject: [PATCH 06/10] chore: remove GitHub link and Overview heading; improve tests and README Co-Authored-By: Claude Sonnet 4.6 --- motoko/canister_logs/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/motoko/canister_logs/README.md b/motoko/canister_logs/README.md index 4af2e716c..aa9724838 100644 --- a/motoko/canister_logs/README.md +++ b/motoko/canister_logs/README.md @@ -1,9 +1,5 @@ # Canister logs -[View this sample's code on GitHub](https://github.com/dfinity/examples/tree/master/motoko/canister_logs) - -## Overview - This example demonstrates canister logging on the Internet Computer. Every message written with `Debug.print` and every trap is recorded in the canister's log, which can be retrieved at any time with `icp canister logs`. The example covers: - **Update calls** — `Debug.print` output from update methods From 87d418e6e5252b0c33d8f127a99ec9773ed07bca Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 10 Jun 2026 21:55:22 +0200 Subject: [PATCH 07/10] chore: replace 'dapp' with 'app' Co-Authored-By: Claude Sonnet 4.6 --- motoko/canister_logs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/motoko/canister_logs/README.md b/motoko/canister_logs/README.md index aa9724838..8181335e6 100644 --- a/motoko/canister_logs/README.md +++ b/motoko/canister_logs/README.md @@ -56,4 +56,4 @@ To watch logs stream in real-time while calling methods in a separate terminal: ## Security considerations and best practices -Refer to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for information on security and best practices for your ICP dapp. +Refer to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for information on security and best practices for your ICP app. From 4943791557d80522ad0a3d815797189423bc4675 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 11 Jun 2026 00:45:30 +0200 Subject: [PATCH 08/10] chore: bump icp-dev-env to 0.3.2 Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/canister_logs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/canister_logs.yml b/.github/workflows/canister_logs.yml index e482591e2..52eb58c5f 100644 --- a/.github/workflows/canister_logs.yml +++ b/.github/workflows/canister_logs.yml @@ -15,7 +15,7 @@ concurrency: jobs: motoko-canister_logs: runs-on: ubuntu-24.04 - container: ghcr.io/dfinity/icp-dev-env-motoko:0.3.1 + container: ghcr.io/dfinity/icp-dev-env-motoko:0.3.2 env: ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: From 80a99d7a757672031c5ef39bf6e5eaafc816e650 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 11 Jun 2026 00:50:46 +0200 Subject: [PATCH 09/10] revert: back to icp-dev-env 0.3.1 (0.3.2 not yet on ghcr.io) Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/canister_logs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/canister_logs.yml b/.github/workflows/canister_logs.yml index 52eb58c5f..e482591e2 100644 --- a/.github/workflows/canister_logs.yml +++ b/.github/workflows/canister_logs.yml @@ -15,7 +15,7 @@ concurrency: jobs: motoko-canister_logs: runs-on: ubuntu-24.04 - container: ghcr.io/dfinity/icp-dev-env-motoko:0.3.2 + container: ghcr.io/dfinity/icp-dev-env-motoko:0.3.1 env: ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: From 69d19344f52400c2531cf249fe91911e09a93fb7 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 11 Jun 2026 01:21:14 +0200 Subject: [PATCH 10/10] chore: bump icp-dev-env to 0.3.2 (images now published) Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/canister_logs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/canister_logs.yml b/.github/workflows/canister_logs.yml index e482591e2..52eb58c5f 100644 --- a/.github/workflows/canister_logs.yml +++ b/.github/workflows/canister_logs.yml @@ -15,7 +15,7 @@ concurrency: jobs: motoko-canister_logs: runs-on: ubuntu-24.04 - container: ghcr.io/dfinity/icp-dev-env-motoko:0.3.1 + container: ghcr.io/dfinity/icp-dev-env-motoko:0.3.2 env: ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: