diff --git a/CHANGELOG.md b/CHANGELOG.md index 4992fa98..8576af71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +* feat: `icp canister logs` supports filtering by timestamp (`--since`, `--until`) and log index (`--since-index`, `--until-index`) +* feat: Support `log_memory_limit` canister setting in `icp canister settings update` and `icp canister settings sync` * feat: Leaving off the method name parameter in `icp canister call` prompts you with an interactive list of methods * fix: Correct templating of special HTML characters in recipes diff --git a/Cargo.lock b/Cargo.lock index 4218178e..c4e0895f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,7 +16,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -434,6 +434,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "backoff" version = "0.4.0" @@ -816,12 +838,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "801927ee168e17809ab8901d9f01f700cd7d8d6a6527997fee44e4b0327a253c" dependencies = [ "ahash 0.8.12", + "cached_proc_macro", + "cached_proc_macro_types", "hashbrown 0.15.5", "once_cell", "thiserror 2.0.18", "web-time", ] +[[package]] +name = "cached_proc_macro" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9225bdcf4e4a9a4c08bf16607908eb2fbf746828d5e0b5e019726dbf6571f201" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "cached_proc_macro_types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" + [[package]] name = "camino" version = "1.2.2" @@ -978,6 +1020,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" @@ -990,6 +1038,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", +] + [[package]] name = "chrono" version = "0.4.43" @@ -1062,6 +1121,15 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1078,6 +1146,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1183,6 +1261,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -1294,7 +1381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", @@ -1715,6 +1802,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.1" @@ -2005,6 +2101,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -2176,11 +2278,25 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.0", + "wasip2", + "wasip3", +] + [[package]] name = "git2" version = "0.20.4" @@ -2191,7 +2307,7 @@ dependencies = [ "libc", "libgit2-sys", "log", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "url", ] @@ -2728,7 +2844,6 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", ] [[package]] @@ -2750,9 +2865,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2796,9 +2913,9 @@ dependencies = [ [[package]] name = "ic-agent" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20a6173286a80fc478462fc45de42faf37a79b0109a489743aeffb3e4a2fc772" +checksum = "da4fb6055538a9f0ee634f0be865cafbe79e74b8addd1eeb05ba0b51d4e8271f" dependencies = [ "arc-swap", "async-channel 2.5.0", @@ -2809,7 +2926,6 @@ dependencies = [ "bytes", "cached", "candid", - "der", "ecdsa", "ed25519-consensus", "elliptic-curve", @@ -2819,7 +2935,7 @@ dependencies = [ "http-body", "http-body-util", "ic-certification", - "ic-ed25519", + "ic-ed25519 0.6.0", "ic-transport-types", "ic-verify-bls-signature", "k256", @@ -2827,9 +2943,9 @@ dependencies = [ "p256", "pem", "pkcs8", - "rand 0.8.5", + "rand 0.10.0", "rangemap", - "reqwest", + "reqwest 0.13.2", "sec1", "serde", "serde_bytes", @@ -2846,9 +2962,9 @@ dependencies = [ [[package]] name = "ic-asset" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cc3c51dd0182931fe12cd9b4b4553d2ff8e42f279a3008f3ceae6680ad6a013" +checksum = "962a5f1992104aa7aec190ff6debbb3cce93a4cf73772c0e682b752ad7eca1ec" dependencies = [ "backoff", "brotli", @@ -2886,7 +3002,7 @@ dependencies = [ "ic-cdk-executor", "ic-cdk-macros", "ic-error-types", - "ic-management-canister-types", + "ic-management-canister-types 0.5.0", "ic0", "pin-project-lite", "serde", @@ -2948,6 +3064,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ic-ed25519" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ad3d86f2e35cb99ae7497b3e4fa92ad751a8e8978f0680d37b86dd51ef91714" +dependencies = [ + "curve25519-dalek", + "ed25519-dalek", + "hex-literal", + "hkdf", + "ic_principal", + "pem", + "rand 0.8.5", + "thiserror 2.0.18", + "zeroize", +] + [[package]] name = "ic-error-types" version = "0.2.0" @@ -2961,9 +3094,9 @@ dependencies = [ [[package]] name = "ic-identity-hsm" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cadfa7095085405ceaadc8aa7714e313cb778d1b98292dbfe23cd087b345b35" +checksum = "b6753cf0b4b1c8abf9c900bd2dc233635a10f800fb12f27ffe39212d8d2458c7" dependencies = [ "hex", "ic-agent", @@ -2999,6 +3132,17 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ic-management-canister-types" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51705516ed4d23f24e8d714a70fe9d7ec17106cfd830d5434a1b29f583ef70ee" +dependencies = [ + "candid", + "serde", + "serde_bytes", +] + [[package]] name = "ic-stable-structures" version = "0.6.9" @@ -3010,9 +3154,9 @@ dependencies = [ [[package]] name = "ic-transport-types" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a775244756a5d97ff19b08071a946a4b4896904e35deb036bf215e80f2e703d" +checksum = "2957ed6ce101b3b6c4aa65bbcf8e92afdec42758b58cb03c7df76c96db13d0c0" dependencies = [ "candid", "hex", @@ -3028,22 +3172,22 @@ dependencies = [ [[package]] name = "ic-utils" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30c22aaa2924df0321705dc01d408c8b75d1e1deb40b65defd2ff04008a720be" +checksum = "99534a857fd9999b9adfde82b2623bb9925997dc1813ec315bd3b4085911104a" dependencies = [ "async-trait", "candid", "futures-util", "ic-agent", - "ic-management-canister-types", + "ic-management-canister-types 0.7.1", "once_cell", "semver", "serde", "serde_bytes", "sha2 0.10.9", - "strum 0.27.2", - "strum_macros 0.27.2", + "strum 0.28.0", + "strum_macros 0.28.0", "thiserror 2.0.18", "time", ] @@ -3123,10 +3267,10 @@ dependencies = [ "hex", "ic-agent", "ic-asset", - "ic-ed25519", + "ic-ed25519 0.5.0", "ic-identity-hsm", "ic-ledger-types", - "ic-management-canister-types", + "ic-management-canister-types 0.7.1", "ic-utils", "icp-canister-interfaces", "icrc-ledger-types", @@ -3143,7 +3287,7 @@ dependencies = [ "pem", "pkcs8", "rand 0.9.2", - "reqwest", + "reqwest 0.13.2", "schemars 1.2.0", "scrypt", "sec1", @@ -3206,9 +3350,9 @@ dependencies = [ "hex", "httptest", "ic-agent", - "ic-ed25519", + "ic-ed25519 0.5.0", "ic-ledger-types", - "ic-management-canister-types", + "ic-management-canister-types 0.7.1", "ic-utils", "icp", "icp-canister-interfaces", @@ -3229,7 +3373,7 @@ dependencies = [ "predicates", "rand 0.9.2", "regex", - "reqwest", + "reqwest 0.13.2", "sec1", "send_ctrlc", "serde", @@ -3248,7 +3392,7 @@ dependencies = [ "tracing-subscriber", "url", "uuid", - "wasmparser", + "wasmparser 0.240.0", "wslpath2", ] @@ -3369,6 +3513,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -3601,6 +3751,28 @@ dependencies = [ "jiff-tzdb", ] +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.34" @@ -3653,7 +3825,7 @@ dependencies = [ "referencing", "regex", "regex-syntax 0.8.8", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "uuid-simd", @@ -3679,7 +3851,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -3771,6 +3943,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.180" @@ -4385,6 +4563,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "openssl-src" version = "300.5.5+3.5.5" @@ -4862,6 +5046,16 @@ dependencies = [ "unicode-width 0.2.2", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.114", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -4954,6 +5148,7 @@ version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", @@ -4998,6 +5193,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "radium" version = "0.7.0" @@ -5025,6 +5226,17 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.0", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -5063,6 +5275,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "rangemap" version = "1.7.1" @@ -5225,18 +5443,55 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", "hyper-rustls", "hyper-util", "js-sys", "log", + "mime", "percent-encoding", "pin-project-lite", "quinn", "rustls", "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", - "serde_urlencoded", "sync_wrapper", "tokio", "tokio-rustls", @@ -5249,7 +5504,6 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", ] [[package]] @@ -5398,14 +5652,26 @@ version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ + "aws-lc-rs", "once_cell", - "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.1", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + [[package]] name = "rustls-pki-types" version = "1.14.0" @@ -5416,12 +5682,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 3.5.1", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -5475,6 +5769,15 @@ dependencies = [ "sdd", ] +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schema-gen" version = "0.2.0" @@ -5862,7 +6165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -5884,7 +6187,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.9.0", "opaque-debug", ] @@ -5896,7 +6199,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -6139,6 +6442,12 @@ dependencies = [ "strum_macros 0.27.2", ] +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" + [[package]] name = "strum_macros" version = "0.26.4" @@ -6164,6 +6473,18 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "subtle" version = "2.6.1" @@ -6232,6 +6553,27 @@ dependencies = [ "windows", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -6458,9 +6800,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -6891,6 +7233,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.108" @@ -6950,11 +7301,33 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser 0.244.0", +] + [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", @@ -6976,6 +7349,18 @@ dependencies = [ "serde", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver", +] + [[package]] name = "web-sys" version = "0.3.85" @@ -6997,10 +7382,10 @@ dependencies = [ ] [[package]] -name = "webpki-roots" -version = "1.0.5" +name = "webpki-root-certs" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" dependencies = [ "rustls-pki-types", ] @@ -7139,6 +7524,17 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -7175,6 +7571,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -7211,6 +7616,21 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -7253,6 +7673,12 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -7265,6 +7691,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -7277,6 +7709,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -7301,6 +7739,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -7313,6 +7757,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -7325,6 +7775,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -7337,6 +7793,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -7373,6 +7835,88 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.13.0", + "prettyplease", + "syn 2.0.114", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.114", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser 0.244.0", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.244.0", +] [[package]] name = "writeable" diff --git a/Cargo.toml b/Cargo.toml index d57d8fdd..2b4ae78a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,15 +43,15 @@ glob = "0.3.2" handlebars = "6.3.2" hex = { version = "0.4.3", features = ["serde"] } httptest = "0.16.3" -ic-agent = { version = "0.45.0" } -ic-asset = "0.27.0" +ic-agent = { version = "0.46.0" } +ic-asset = "0.28.0" ic-ed25519 = "0.5.0" ic-ledger-types = "0.16.0" -ic-management-canister-types = { version = "0.5.0" } -ic-utils = { version = "0.45.0" } +ic-management-canister-types = { version = "0.7.1" } +ic-utils = { version = "0.46.0" } icp = { path = "crates/icp" } icp-canister-interfaces = { path = "crates/icp-canister-interfaces" } -ic-identity-hsm = "0.45.0" +ic-identity-hsm = "0.46.0" icrc-ledger-types = "0.1.10" indicatif = "0.18.0" indoc = "2.0.6" @@ -98,9 +98,9 @@ wslpath2 = "0.1" zeroize = "1.8.1" [workspace.dependencies.reqwest] -version = "0.12.15" +version = "0.13.2" default-features = false -features = ["rustls-tls"] +features = ["rustls", "json", "stream"] [workspace.dependencies.schemars] version = "1.0.4" diff --git a/crates/icp-cli/src/commands/canister/logs.rs b/crates/icp-cli/src/commands/canister/logs.rs index b991b2dd..283b7d6e 100644 --- a/crates/icp-cli/src/commands/canister/logs.rs +++ b/crates/icp-cli/src/commands/canister/logs.rs @@ -1,7 +1,9 @@ use anyhow::{Context as _, anyhow}; use clap::Args; -use ic_management_canister_types::{CanisterLogRecord, FetchCanisterLogsResult}; use ic_utils::interfaces::ManagementCanister; +use ic_utils::interfaces::management_canister::{ + CanisterLogFilter, CanisterLogRecord, FetchCanisterLogsArgs, FetchCanisterLogsResult, +}; use icp::context::Context; use icp::signal::stop_signal; use time::{OffsetDateTime, format_description::well_known::Rfc3339}; @@ -22,6 +24,51 @@ pub(crate) struct LogsArgs { /// Polling interval in seconds when following logs (requires --follow) #[arg(long, requires = "follow", default_value = "2")] pub(crate) interval: u64, + + /// Show logs at or after this timestamp (inclusive). Accepts nanoseconds since Unix epoch or RFC3339 + /// (e.g. '2024-01-01T00:00:00Z'). Cannot be used with --follow + #[arg(long, value_name = "TIMESTAMP", conflicts_with_all = ["follow", "since_index", "until_index"], value_parser = parse_timestamp)] + pub(crate) since: Option, + + /// Show logs before this timestamp (exclusive). Accepts nanoseconds since Unix epoch or RFC3339 + /// (e.g. '2024-01-01T00:00:00Z'). Cannot be used with --follow + #[arg(long, value_name = "TIMESTAMP", conflicts_with_all = ["follow", "since_index", "until_index"], value_parser = parse_timestamp)] + pub(crate) until: Option, + + /// Show logs at or after this log index (inclusive). Cannot be used with --follow + #[arg(long, value_name = "INDEX", conflicts_with_all = ["follow", "since", "until"])] + pub(crate) since_index: Option, + + /// Show logs before this log index (exclusive). Cannot be used with --follow + #[arg(long, value_name = "INDEX", conflicts_with_all = ["follow", "since", "until"])] + pub(crate) until_index: Option, +} + +fn parse_timestamp(s: &str) -> Result { + // Try raw nanoseconds first + if let Ok(nanos) = s.parse::() { + return Ok(nanos); + } + // Detect numeric overflow before falling back to RFC3339 + if s.parse::().is_ok() { + return Err(format!( + "'{s}' overflows the nanosecond timestamp range (u64)" + )); + } + // Fall back to RFC3339 + let dt = OffsetDateTime::parse(s, &Rfc3339) + .map_err(|_| format!("'{s}' is not a valid nanosecond timestamp or RFC3339 datetime"))?; + let nanos = dt.unix_timestamp_nanos(); + u64::try_from(nanos).map_err(|_| { + if nanos < 0 { + format!( + "'{s}' is before the Unix epoch; timestamp must be a non-negative number \ + or an RFC3339 datetime at or after 1970-01-01T00:00:00Z" + ) + } else { + format!("'{s}' overflows the nanosecond timestamp range (u64)") + } + }) } pub(crate) async fn exec(ctx: &Context, args: &LogsArgs) -> Result<(), anyhow::Error> { @@ -55,7 +102,41 @@ pub(crate) async fn exec(ctx: &Context, args: &LogsArgs) -> Result<(), anyhow::E follow_logs(ctx, &mgmt, &canister_id, args.interval).await } else { // Single fetch mode: fetch all logs once - fetch_and_display_logs(ctx, &mgmt, &canister_id).await + fetch_and_display_logs(ctx, &mgmt, &canister_id, build_filter(args)?).await + } +} + +fn build_filter(args: &LogsArgs) -> Result, anyhow::Error> { + if args.since_index.is_some() || args.until_index.is_some() { + let start = args.since_index.unwrap_or(0); + let end = args.until_index.unwrap_or(u64::MAX); + if end == 0 { + return Err(anyhow!( + "--until-index must be greater than 0 (the end bound is exclusive)" + )); + } + if start >= end { + return Err(anyhow!( + "--since-index ({start}) must be less than --until-index ({end})" + )); + } + Ok(Some(CanisterLogFilter::ByIdx { start, end })) + } else if args.since.is_some() || args.until.is_some() { + let start = args.since.unwrap_or(0); + let end = args.until.unwrap_or(u64::MAX); + if end == 0 { + return Err(anyhow!( + "--until must be greater than 0 (the end bound is exclusive)" + )); + } + if start >= end { + return Err(anyhow!( + "--since timestamp must be less than --until timestamp" + )); + } + Ok(Some(CanisterLogFilter::ByTimestampNanos { start, end })) + } else { + Ok(None) } } @@ -63,9 +144,14 @@ async fn fetch_and_display_logs( ctx: &Context, mgmt: &ManagementCanister<'_>, canister_id: &candid::Principal, + filter: Option, ) -> Result<(), anyhow::Error> { + let fetch_args = FetchCanisterLogsArgs { + canister_id: *canister_id, + filter, + }; let (result,): (FetchCanisterLogsResult,) = mgmt - .fetch_canister_logs(canister_id) + .fetch_canister_logs(&fetch_args) .await .context("Failed to fetch canister logs")?; @@ -77,6 +163,8 @@ async fn fetch_and_display_logs( Ok(()) } +const FOLLOW_LOOKBACK_NANOS: u64 = 60 * 60 * 1_000_000_000; // 1 hour + async fn follow_logs( ctx: &Context, mgmt: &ManagementCanister<'_>, @@ -87,21 +175,34 @@ async fn follow_logs( let interval = std::time::Duration::from_secs(interval_seconds); loop { - // Fetch all logs + let filter = match last_idx { + Some(idx) => Some(CanisterLogFilter::ByIdx { + start: idx + 1, // Start from the next log index after the last one we displayed + end: u64::MAX, + }), + None => { + // First fetch: look back 1 hour from now + let now_nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .ok() + .and_then(|d| u64::try_from(d.as_nanos()).ok()) + .unwrap_or(0); + Some(CanisterLogFilter::ByTimestampNanos { + start: now_nanos.saturating_sub(FOLLOW_LOOKBACK_NANOS), + end: u64::MAX, + }) + } + }; + let fetch_args = FetchCanisterLogsArgs { + canister_id: *canister_id, + filter, + }; let (result,): (FetchCanisterLogsResult,) = mgmt - .fetch_canister_logs(canister_id) + .fetch_canister_logs(&fetch_args) .await .context("Failed to fetch canister logs")?; - // Filter to only new logs based on last_idx - let new_logs: Vec<_> = result - .canister_log_records - .into_iter() - .filter(|log| match last_idx { - None => true, // First iteration, show all logs - Some(idx) => log.idx > idx, - }) - .collect(); + let new_logs = result.canister_log_records; if !new_logs.is_empty() { for log in &new_logs { @@ -174,6 +275,10 @@ fn format_content(content: &[u8]) -> String { mod tests { use super::*; + /// 2024-01-01T10:00:00.123456789Z as nanoseconds since Unix epoch. + const TEST_TIMESTAMP_NANOS: u64 = 1_704_103_200_123_456_789; + const TEST_TIMESTAMP_RFC3339: &str = "2024-01-01T10:00:00.123456789Z"; + #[test] fn test_format_content_valid_utf8() { let content = b"Hello, World!"; @@ -198,23 +303,198 @@ mod tests { #[test] fn test_format_timestamp() { - // Test timestamp: 2024-01-01T10:00:00.123456789Z - let nanos = 1704103200123456789u64; - let formatted = format_timestamp(nanos); - assert_eq!(formatted, "2024-01-01T10:00:00.123456789Z"); + let formatted = format_timestamp(TEST_TIMESTAMP_NANOS); + assert_eq!(formatted, TEST_TIMESTAMP_RFC3339); } #[test] fn test_format_log() { let log = CanisterLogRecord { idx: 42, - timestamp_nanos: 1704103200123456789, + timestamp_nanos: TEST_TIMESTAMP_NANOS, content: b"Test message".to_vec(), }; let formatted = format_log(&log); assert_eq!( formatted, - "[42. 2024-01-01T10:00:00.123456789Z]: Test message" + format!("[42. {TEST_TIMESTAMP_RFC3339}]: Test message") + ); + } + + #[test] + fn test_parse_timestamp_raw_nanos() { + assert_eq!( + parse_timestamp(&TEST_TIMESTAMP_NANOS.to_string()), + Ok(TEST_TIMESTAMP_NANOS) + ); + assert_eq!(parse_timestamp("0"), Ok(0)); + } + + #[test] + fn test_parse_timestamp_rfc3339() { + // 2024-01-01T10:00:00Z = 1704103200000000000 nanos + assert_eq!( + parse_timestamp("2024-01-01T10:00:00Z"), + Ok(1_704_103_200_000_000_000) + ); + } + + #[test] + fn test_parse_timestamp_rfc3339_with_nanos() { + assert_eq!( + parse_timestamp(TEST_TIMESTAMP_RFC3339), + Ok(TEST_TIMESTAMP_NANOS) + ); + } + + #[test] + fn test_parse_timestamp_invalid() { + assert!(parse_timestamp("not-a-timestamp").is_err()); + } + + #[test] + fn test_parse_timestamp_numeric_overflow() { + let result = parse_timestamp("99999999999999999999999"); + assert!(result.is_err()); + assert!( + result + .unwrap_err() + .contains("overflows the nanosecond timestamp range") ); } + + #[test] + fn test_parse_timestamp_before_epoch() { + let result = parse_timestamp("1969-12-31T23:59:59Z"); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("before the Unix epoch")); + } + + fn make_logs_args( + since: Option, + until: Option, + since_index: Option, + until_index: Option, + ) -> LogsArgs { + LogsArgs { + cmd_args: args::CanisterCommandArgs { + canister: args::Canister::Name("test".to_string()), + network: Default::default(), + environment: Default::default(), + identity: Default::default(), + }, + follow: false, + interval: 2, + since, + until, + since_index, + until_index, + } + } + + #[test] + fn build_filter_no_flags() { + let args = make_logs_args(None, None, None, None); + assert!(build_filter(&args).unwrap().is_none()); + } + + #[test] + fn build_filter_since_index_only() { + let args = make_logs_args(None, None, Some(5), None); + let filter = build_filter(&args).unwrap().unwrap(); + assert!(matches!( + filter, + CanisterLogFilter::ByIdx { + start: 5, + end: u64::MAX + } + )); + } + + #[test] + fn build_filter_until_index_only() { + let args = make_logs_args(None, None, None, Some(10)); + let filter = build_filter(&args).unwrap().unwrap(); + assert!(matches!( + filter, + CanisterLogFilter::ByIdx { start: 0, end: 10 } + )); + } + + #[test] + fn build_filter_both_indices() { + let args = make_logs_args(None, None, Some(3), Some(7)); + let filter = build_filter(&args).unwrap().unwrap(); + assert!(matches!( + filter, + CanisterLogFilter::ByIdx { start: 3, end: 7 } + )); + } + + #[test] + fn build_filter_inverted_indices_error() { + let args = make_logs_args(None, None, Some(10), Some(5)); + let err = build_filter(&args).unwrap_err().to_string(); + assert!(err.contains("--since-index (10) must be less than --until-index (5)")); + } + + #[test] + fn build_filter_since_timestamp_only() { + let args = make_logs_args(Some(1000), None, None, None); + let filter = build_filter(&args).unwrap().unwrap(); + assert!(matches!( + filter, + CanisterLogFilter::ByTimestampNanos { + start: 1000, + end: u64::MAX + } + )); + } + + #[test] + fn build_filter_until_timestamp_only() { + let args = make_logs_args(None, Some(2000), None, None); + let filter = build_filter(&args).unwrap().unwrap(); + assert!(matches!( + filter, + CanisterLogFilter::ByTimestampNanos { + start: 0, + end: 2000 + } + )); + } + + #[test] + fn build_filter_both_timestamps() { + let args = make_logs_args(Some(1000), Some(2000), None, None); + let filter = build_filter(&args).unwrap().unwrap(); + assert!(matches!( + filter, + CanisterLogFilter::ByTimestampNanos { + start: 1000, + end: 2000 + } + )); + } + + #[test] + fn build_filter_inverted_timestamps_error() { + let args = make_logs_args(Some(2000), Some(1000), None, None); + let err = build_filter(&args).unwrap_err().to_string(); + assert!(err.contains("--since timestamp must be less than --until timestamp")); + } + + #[test] + fn build_filter_until_index_zero_error() { + let args = make_logs_args(None, None, None, Some(0)); + let err = build_filter(&args).unwrap_err().to_string(); + assert!(err.contains("--until-index must be greater than 0")); + } + + #[test] + fn build_filter_until_timestamp_zero_error() { + let args = make_logs_args(None, Some(0), None, None); + let err = build_filter(&args).unwrap_err().to_string(); + assert!(err.contains("--until must be greater than 0")); + } } diff --git a/crates/icp-cli/src/commands/canister/settings/update.rs b/crates/icp-cli/src/commands/canister/settings/update.rs index 2bd7d8ce..57417f25 100644 --- a/crates/icp-cli/src/commands/canister/settings/update.rs +++ b/crates/icp-cli/src/commands/canister/settings/update.rs @@ -120,6 +120,11 @@ pub(crate) struct UpdateArgs { #[arg(long)] wasm_memory_threshold: Option, + /// Log memory limit in bytes (max 2 MiB). Oldest logs are purged when usage exceeds this value. + /// Supports suffixes: kb, kib, mb, mib (e.g. "2mib" or "256kib"). Canister default is 4096 bytes. + #[arg(long)] + log_memory_limit: Option, + #[command(flatten)] log_visibility: Option, @@ -266,6 +271,14 @@ pub(crate) async fn exec(ctx: &Context, args: &UpdateArgs) -> Result<(), anyhow: } update = update.with_wasm_memory_threshold(wasm_memory_threshold.get()); } + if let Some(log_memory_limit) = &args.log_memory_limit { + if configured_settings.log_memory_limit.is_some() { + ctx.term.write_line( + "Warning: Log memory limit is already set in icp.yaml; this new value will be overridden on next settings sync" + )? + } + update = update.with_log_memory_limit(log_memory_limit.get()); + } if let Some(log_visibility) = log_visibility { if configured_settings.log_visibility.is_some() { ctx.term.write_line( diff --git a/crates/icp-cli/src/commands/canister/snapshot/create.rs b/crates/icp-cli/src/commands/canister/snapshot/create.rs index 10f2c3a9..056c1c54 100644 --- a/crates/icp-cli/src/commands/canister/snapshot/create.rs +++ b/crates/icp-cli/src/commands/canister/snapshot/create.rs @@ -58,9 +58,11 @@ pub(crate) async fn exec(ctx: &Context, args: &CreateArgs) -> Result<(), anyhow: let take_args = TakeCanisterSnapshotArgs { canister_id: cid, replace_snapshot: args.replace.as_ref().map(|s| s.0.clone()), + uninstall_code: None, + sender_canister_version: None, }; - let (snapshot,) = mgmt.take_canister_snapshot(&cid, &take_args).await?; + let (snapshot,) = mgmt.take_canister_snapshot(&take_args).await?; ctx.term.write_line(&format!( "Created snapshot {id} for canister {name} ({cid})", diff --git a/crates/icp-cli/src/commands/canister/snapshot/delete.rs b/crates/icp-cli/src/commands/canister/snapshot/delete.rs index 5c8bef52..dc33efec 100644 --- a/crates/icp-cli/src/commands/canister/snapshot/delete.rs +++ b/crates/icp-cli/src/commands/canister/snapshot/delete.rs @@ -41,7 +41,7 @@ pub(crate) async fn exec(ctx: &Context, args: &DeleteArgs) -> Result<(), anyhow: snapshot_id: args.snapshot_id.0.clone(), }; - mgmt.delete_canister_snapshot(&cid, &delete_args).await?; + mgmt.delete_canister_snapshot(&delete_args).await?; let name = &args.cmd_args.canister; ctx.term.write_line(&format!( diff --git a/crates/icp-cli/src/commands/canister/snapshot/restore.rs b/crates/icp-cli/src/commands/canister/snapshot/restore.rs index dd54c327..3f2d79c1 100644 --- a/crates/icp-cli/src/commands/canister/snapshot/restore.rs +++ b/crates/icp-cli/src/commands/canister/snapshot/restore.rs @@ -58,7 +58,7 @@ pub(crate) async fn exec(ctx: &Context, args: &RestoreArgs) -> Result<(), anyhow sender_canister_version: None, }; - mgmt.load_canister_snapshot(&cid, &load_args).await?; + mgmt.load_canister_snapshot(&load_args).await?; ctx.term.write_line(&format!( "Restored canister {name} ({cid}) from snapshot {id}", diff --git a/crates/icp-cli/src/commands/canister/status.rs b/crates/icp-cli/src/commands/canister/status.rs index ebc71cda..6d603352 100644 --- a/crates/icp-cli/src/commands/canister/status.rs +++ b/crates/icp-cli/src/commands/canister/status.rs @@ -325,6 +325,7 @@ struct SerializableCanisterSettings { reserved_cycles_limit: String, wasm_memory_limit: String, wasm_memory_threshold: String, + log_memory_limit: String, log_visibility: SerializableLogVisibility, environment_variables: Vec, } @@ -377,6 +378,7 @@ impl SerializableCanisterSettings { reserved_cycles_limit: settings.reserved_cycles_limit.to_string(), wasm_memory_limit: settings.wasm_memory_limit.to_string(), wasm_memory_threshold: settings.wasm_memory_threshold.to_string(), + log_memory_limit: settings.log_memory_limit.to_string(), log_visibility: SerializableLogVisibility::from(&settings.log_visibility), environment_variables: settings.environment_variables.clone(), } @@ -471,6 +473,11 @@ fn build_output(result: &SerializableCanisterStatusResult) -> Result "Controllers".to_string(), diff --git a/crates/icp-cli/src/operations/settings.rs b/crates/icp-cli/src/operations/settings.rs index 651e6b8c..27bd300d 100644 --- a/crates/icp-cli/src/operations/settings.rs +++ b/crates/icp-cli/src/operations/settings.rs @@ -85,6 +85,7 @@ pub(crate) async fn sync_settings( ref reserved_cycles_limit, ref wasm_memory_limit, ref wasm_memory_threshold, + ref log_memory_limit, ref environment_variables, } = &canister.settings; let current_settings = status.settings; @@ -134,6 +135,10 @@ pub(crate) async fn sync_settings( .as_ref() .map(|m| m.get()) .is_none_or(|s| current_settings.wasm_memory_threshold.0.to_u64() == Some(s)) + && log_memory_limit + .as_ref() + .map(|m| m.get()) + .is_none_or(|s| current_settings.log_memory_limit.0.to_u64() == Some(s)) && environment_variable_setting .as_ref() .is_none_or(|s| environment_variables_eq(s, ¤t_settings.environment_variables)) @@ -141,15 +146,35 @@ pub(crate) async fn sync_settings( // No changes needed return Ok(()); } - mgmt.update_settings(cid) - .with_optional_log_visibility(log_visibility_setting) - .with_optional_compute_allocation(compute_allocation) - .with_optional_memory_allocation(memory_allocation.as_ref().map(|m| m.get())) - .with_optional_freezing_threshold(freezing_threshold.as_ref().map(|d| d.get())) - .with_optional_reserved_cycles_limit(reserved_cycles_limit.as_ref().map(|r| r.get())) - .with_optional_wasm_memory_limit(wasm_memory_limit.as_ref().map(|m| m.get())) - .with_optional_wasm_memory_threshold(wasm_memory_threshold.as_ref().map(|m| m.get())) - .with_optional_environment_variables(environment_variable_setting) + let mut builder = mgmt.update_settings(cid); + if let Some(v) = log_visibility_setting { + builder = builder.with_log_visibility(v); + } + if let Some(v) = compute_allocation { + builder = builder.with_compute_allocation(v); + } + if let Some(v) = memory_allocation.as_ref().map(|m| m.get()) { + builder = builder.with_memory_allocation(v); + } + if let Some(v) = freezing_threshold.as_ref().map(|d| d.get()) { + builder = builder.with_freezing_threshold(v); + } + if let Some(v) = reserved_cycles_limit.as_ref().map(|r| r.get()) { + builder = builder.with_reserved_cycles_limit(v); + } + if let Some(v) = wasm_memory_limit.as_ref().map(|m| m.get()) { + builder = builder.with_wasm_memory_limit(v); + } + if let Some(v) = wasm_memory_threshold.as_ref().map(|m| m.get()) { + builder = builder.with_wasm_memory_threshold(v); + } + if let Some(v) = log_memory_limit.as_ref().map(|m| m.get()) { + builder = builder.with_log_memory_limit(v); + } + if let Some(v) = environment_variable_setting { + builder = builder.with_environment_variables(v); + } + builder .build() .context(ValidateSettingsSnafu { name: &canister.name, diff --git a/crates/icp-cli/src/operations/snapshot_transfer.rs b/crates/icp-cli/src/operations/snapshot_transfer.rs index 7413b705..ddaf9c4a 100644 --- a/crates/icp-cli/src/operations/snapshot_transfer.rs +++ b/crates/icp-cli/src/operations/snapshot_transfer.rs @@ -439,12 +439,9 @@ pub async fn read_snapshot_metadata( snapshot_id: snapshot_id.to_vec(), }; - let (metadata,) = with_retry(|| async { - mgmt.read_canister_snapshot_metadata(&canister_id, &args) - .await - }) - .await - .context(ReadMetadataSnafu { canister_id })?; + let (metadata,) = with_retry(|| async { mgmt.read_canister_snapshot_metadata(&args).await }) + .await + .context(ReadMetadataSnafu { canister_id })?; Ok(metadata) } @@ -480,12 +477,9 @@ pub async fn upload_snapshot_metadata( on_low_wasm_memory_hook_status: metadata.on_low_wasm_memory_hook_status.clone(), }; - let (result,) = with_retry(|| async { - mgmt.upload_canister_snapshot_metadata(&canister_id, &args) - .await - }) - .await - .context(UploadMetadataSnafu { canister_id })?; + let (result,) = with_retry(|| async { mgmt.upload_canister_snapshot_metadata(&args).await }) + .await + .context(UploadMetadataSnafu { canister_id })?; Ok(result) } @@ -559,13 +553,11 @@ pub async fn download_blob_to_file( let mgmt = mgmt.clone(); in_progress.push(async move { - let result = with_retry(|| async { - mgmt.read_canister_snapshot_data(&canister_id, &args).await - }) - .await - .context(ReadDataChunkSnafu { - offset: chunk_offset, - })?; + let result = with_retry(|| async { mgmt.read_canister_snapshot_data(&args).await }) + .await + .context(ReadDataChunkSnafu { + offset: chunk_offset, + })?; Ok::<_, SnapshotTransferError>((chunk_offset, result.0.chunk)) }); } @@ -629,10 +621,9 @@ pub async fn download_wasm_chunk( let hash_hex = hex::encode(&chunk_hash.hash); let output_path = paths.wasm_chunk_path(&chunk_hash.hash); - let (result,) = - with_retry(|| async { mgmt.read_canister_snapshot_data(&canister_id, &args).await }) - .await - .context(ReadWasmChunkSnafu { hash: &hash_hex })?; + let (result,) = with_retry(|| async { mgmt.read_canister_snapshot_data(&args).await }) + .await + .context(ReadWasmChunkSnafu { hash: &hash_hex })?; icp::fs::write(&output_path, &result.chunk)?; @@ -706,12 +697,9 @@ pub async fn upload_blob_from_file( let mgmt = mgmt.clone(); in_progress.push(async move { - with_retry(|| async { - mgmt.upload_canister_snapshot_data(&canister_id, &args) - .await - }) - .await - .context(UploadDataChunkSnafu { offset })?; + with_retry(|| async { mgmt.upload_canister_snapshot_data(&args).await }) + .await + .context(UploadDataChunkSnafu { offset })?; Ok::<_, SnapshotTransferError>((offset, args.chunk.len() as u64)) }); } @@ -782,12 +770,9 @@ pub async fn upload_wasm_chunk( let hash_hex = hex::encode(chunk_hash); - with_retry(|| async { - mgmt.upload_canister_snapshot_data(&canister_id, &args) - .await - }) - .await - .context(UploadWasmChunkSnafu { hash: hash_hex })?; + with_retry(|| async { mgmt.upload_canister_snapshot_data(&args).await }) + .await + .context(UploadWasmChunkSnafu { hash: hash_hex })?; Ok(()) } diff --git a/crates/icp-cli/tests/canister_logs_tests.rs b/crates/icp-cli/tests/canister_logs_tests.rs index 12db5553..b84484b7 100644 --- a/crates/icp-cli/tests/canister_logs_tests.rs +++ b/crates/icp-cli/tests/canister_logs_tests.rs @@ -3,6 +3,7 @@ use { crate::common::{ENVIRONMENT_RANDOM_PORT, NETWORK_RANDOM_PORT, TestContext}, icp::fs::write_string, indoc::formatdoc, + predicates::prelude::PredicateBooleanExt, predicates::str::contains, std::time::Duration, }; @@ -163,3 +164,204 @@ async fn canister_logs_follow_mode() { .stdout(contains("4 Repeated")) .stdout(contains("5 Repeated")); } + +#[cfg(unix)] // moc +#[tokio::test] +async fn canister_logs_filter_by_index() { + let ctx = TestContext::new(); + let project_dir = ctx.create_project_dir("canister_logs"); + + ctx.copy_asset_dir("canister_logs", &project_dir); + + let pm = formatdoc! {r#" + canisters: + - name: logger + recipe: + type: "@dfinity/motoko@v4.0.0" + configuration: + main: main.mo + args: "" + + {NETWORK_RANDOM_PORT} + {ENVIRONMENT_RANDOM_PORT} + "#}; + + write_string(&project_dir.join("icp.yaml"), &pm).expect("failed to write project manifest"); + + let _g = ctx.start_network_in(&project_dir, "random-network").await; + ctx.ping_until_healthy(&project_dir, "random-network"); + + ctx.icp() + .current_dir(&project_dir) + .args(["deploy", "logger", "--environment", "random-environment"]) + .assert() + .success(); + + // Create several log entries + for i in 0..=2 { + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "call", + "--environment", + "random-environment", + "logger", + "log", + &format!("(\"Message {i}\")"), + ]) + .assert() + .success(); + } + + // Fetch all logs to verify baseline + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "logs", + "logger", + "--environment", + "random-environment", + ]) + .assert() + .success() + .stdout( + contains("Message 0") + .and(contains("Message 1")) + .and(contains("Message 2")), + ); + + // --since-index is inclusive, so --since-index 1 should include Message 1 and Message 2 but not Message 0 + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "logs", + "logger", + "--since-index", + "1", + "--environment", + "random-environment", + ]) + .assert() + .success() + .stdout(contains("Message 0").not()) + .stdout(contains("Message 1")) + .stdout(contains("Message 2")); + + // --until-index is exclusive, so --until-index 1 should only include Message 0 + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "logs", + "logger", + "--until-index", + "1", + "--environment", + "random-environment", + ]) + .assert() + .success() + .stdout(contains("Message 0")) + .stdout(contains("Message 1").not()) + .stdout(contains("Message 2").not()); +} + +#[cfg(unix)] // moc +#[tokio::test] +async fn canister_logs_filter_by_timestamp() { + let ctx = TestContext::new(); + let project_dir = ctx.create_project_dir("canister_logs"); + + ctx.copy_asset_dir("canister_logs", &project_dir); + + let pm = formatdoc! {r#" + canisters: + - name: logger + recipe: + type: "@dfinity/motoko@v4.0.0" + configuration: + main: main.mo + args: "" + + {NETWORK_RANDOM_PORT} + {ENVIRONMENT_RANDOM_PORT} + "#}; + + write_string(&project_dir.join("icp.yaml"), &pm).expect("failed to write project manifest"); + + let _g = ctx.start_network_in(&project_dir, "random-network").await; + ctx.ping_until_healthy(&project_dir, "random-network"); + + ctx.icp() + .current_dir(&project_dir) + .args(["deploy", "logger", "--environment", "random-environment"]) + .assert() + .success(); + + // Create a log entry + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "call", + "--environment", + "random-environment", + "logger", + "log", + "(\"Timestamped message\")", + ]) + .assert() + .success(); + + // Filter with --since far in the future should return no logs + // Use a large but valid u64 nanosecond value (~year 2286) + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "logs", + "logger", + "--since", + "9999999999999999999", + "--environment", + "random-environment", + ]) + .assert() + .success() + .stdout(contains("Timestamped message").not()); + + // Filter with --since 0 should return all logs + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "logs", + "logger", + "--since", + "0", + "--environment", + "random-environment", + ]) + .assert() + .success() + .stdout(contains("Timestamped message")); + + // RFC3339 timestamp: --since with a past date should include the log + ctx.icp() + .current_dir(&project_dir) + .args([ + "canister", + "logs", + "logger", + "--since", + "2020-01-01T00:00:00Z", + "--environment", + "random-environment", + ]) + .assert() + .success() + .stdout(contains("Timestamped message")); +} diff --git a/crates/icp-cli/tests/canister_settings_tests.rs b/crates/icp-cli/tests/canister_settings_tests.rs index 1e4c8043..2faf8677 100644 --- a/crates/icp-cli/tests/canister_settings_tests.rs +++ b/crates/icp-cli/tests/canister_settings_tests.rs @@ -713,7 +713,11 @@ async fn canister_settings_update_miscellaneous() { .and(contains("Wasm memory threshold: 0")), ); - // Update compute allocation + // Update miscellaneous settings. + // NOTE: `log_memory_limit` is included below, but PocketIC does not yet + // support it — setting it via `update_settings` has no effect, and querying + // the canister settings always returns 0 regardless of the value set. + // When PocketIC gains support, the assertion should change from 0 to 1_048_576. ctx.icp() .current_dir(&project_dir) .args([ @@ -735,6 +739,8 @@ async fn canister_settings_update_miscellaneous() { "4GiB", "--wasm-memory-threshold", "4GiB", + "--log-memory-limit", + "1MiB", ]) .assert() .success(); @@ -759,7 +765,10 @@ async fn canister_settings_update_miscellaneous() { .and(contains("Freezing threshold: 8_640_000")) .and(contains("Reserved cycles limit: 6_000_000_000_000")) .and(contains("Wasm memory limit: 4_294_967_296")) - .and(contains("Wasm memory threshold: 4_294_967_296")), + .and(contains("Wasm memory threshold: 4_294_967_296")) + // PocketIC does not support log_memory_limit yet — always returns 0. + // Update to 1_048_576 once PocketIC support lands. + .and(contains("Log memory limit: 0")), ); } diff --git a/crates/icp/src/canister/mod.rs b/crates/icp/src/canister/mod.rs index 2c568a45..9b9a5a26 100644 --- a/crates/icp/src/canister/mod.rs +++ b/crates/icp/src/canister/mod.rs @@ -231,6 +231,10 @@ pub struct Settings { /// Supports suffixes in YAML: kb, kib, mb, mib, gb, gib (e.g. "4gib" or "2.5kb"). pub wasm_memory_threshold: Option, + /// Log memory limit in bytes (max 2 MiB). Oldest logs are purged when usage exceeds this value. + /// Supports suffixes in YAML: kb, kib, mb, mib (e.g. "2mib" or "256kib"). Canister default is 4096 bytes. + pub log_memory_limit: Option, + /// Environment variables for the canister as key-value pairs. /// These variables are accessible within the canister and can be used to configure /// behavior without hardcoding values in the WASM module. @@ -392,6 +396,26 @@ allowed_viewers: ); } + #[test] + fn settings_log_memory_limit_parses_suffix() { + let yaml = "log_memory_limit: 256kib"; + let settings: Settings = serde_yaml::from_str(yaml).unwrap(); + assert_eq!( + settings.log_memory_limit.as_ref().map(|m| m.get()), + Some(256 * 1024) + ); + } + + #[test] + fn settings_log_memory_limit_parses_mib() { + let yaml = "log_memory_limit: 2mib"; + let settings: Settings = serde_yaml::from_str(yaml).unwrap(); + assert_eq!( + settings.log_memory_limit.as_ref().map(|m| m.get()), + Some(2 * 1024 * 1024) + ); + } + #[test] fn log_visibility_conversion_to_ic_type() { let controllers = LogVisibility::Controllers; diff --git a/deny.toml b/deny.toml index 0529bb78..e734e2e5 100644 --- a/deny.toml +++ b/deny.toml @@ -17,6 +17,7 @@ allow = [ "Zlib", "Unicode-DFS-2016", "Unicode-3.0", + "OpenSSL", ] unused-allowed-license = "allow" diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 5ab7a519..a98e5129 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -333,6 +333,10 @@ Fetch and display canister logs * `--interval ` — Polling interval in seconds when following logs (requires --follow) Default value: `2` +* `--since ` — Show logs at or after this timestamp (inclusive). Accepts nanoseconds since Unix epoch or RFC3339 (e.g. '2024-01-01T00:00:00Z'). Cannot be used with --follow +* `--until ` — Show logs before this timestamp (exclusive). Accepts nanoseconds since Unix epoch or RFC3339 (e.g. '2024-01-01T00:00:00Z'). Cannot be used with --follow +* `--since-index ` — Show logs at or after this log index (inclusive). Cannot be used with --follow +* `--until-index ` — Show logs before this log index (exclusive). Cannot be used with --follow @@ -447,6 +451,7 @@ Change a canister's settings to specified values * `--reserved-cycles-limit ` — Upper limit on cycles reserved for future resource payments. Memory allocations that would push the reserved balance above this limit will fail. Supports suffixes: k (thousand), m (million), b (billion), t (trillion) * `--wasm-memory-limit ` — Wasm memory limit in bytes. Supports suffixes: kb, kib, mb, mib, gb, gib (e.g. "4gib" or "2.5kb") * `--wasm-memory-threshold ` — Wasm memory threshold in bytes. Supports suffixes: kb, kib, mb, mib, gb, gib (e.g. "4gib" or "2.5kb") +* `--log-memory-limit ` — Log memory limit in bytes (max 2 MiB). Oldest logs are purged when usage exceeds this value. Supports suffixes: kb, kib, mb, mib (e.g. "2mib" or "256kib"). Canister default is 4096 bytes * `--log-visibility ` * `--add-log-viewer ` * `--remove-log-viewer ` diff --git a/docs/schemas/canister-yaml-schema.json b/docs/schemas/canister-yaml-schema.json index b0e9035d..5e4982b3 100644 --- a/docs/schemas/canister-yaml-schema.json +++ b/docs/schemas/canister-yaml-schema.json @@ -352,6 +352,17 @@ ], "description": "Freezing threshold in seconds. Controls how long a canister can be inactive before being frozen.\nSupports duration suffixes in YAML: s, m, h, d, w (e.g. \"30d\" or \"4w\")." }, + "log_memory_limit": { + "anyOf": [ + { + "$ref": "#/$defs/MemoryAmount" + }, + { + "type": "null" + } + ], + "description": "Log memory limit in bytes (max 2 MiB). Oldest logs are purged when usage exceeds this value.\nSupports suffixes in YAML: kb, kib, mb, mib (e.g. \"2mib\" or \"256kib\"). Canister default is 4096 bytes." + }, "log_visibility": { "anyOf": [ { @@ -528,6 +539,7 @@ "compute_allocation": null, "environment_variables": null, "freezing_threshold": null, + "log_memory_limit": null, "log_visibility": null, "memory_allocation": null, "reserved_cycles_limit": null, diff --git a/docs/schemas/environment-yaml-schema.json b/docs/schemas/environment-yaml-schema.json index 840b4d86..5dfceca4 100644 --- a/docs/schemas/environment-yaml-schema.json +++ b/docs/schemas/environment-yaml-schema.json @@ -164,6 +164,17 @@ ], "description": "Freezing threshold in seconds. Controls how long a canister can be inactive before being frozen.\nSupports duration suffixes in YAML: s, m, h, d, w (e.g. \"30d\" or \"4w\")." }, + "log_memory_limit": { + "anyOf": [ + { + "$ref": "#/$defs/MemoryAmount" + }, + { + "type": "null" + } + ], + "description": "Log memory limit in bytes (max 2 MiB). Oldest logs are purged when usage exceeds this value.\nSupports suffixes in YAML: kb, kib, mb, mib (e.g. \"2mib\" or \"256kib\"). Canister default is 4096 bytes." + }, "log_visibility": { "anyOf": [ { diff --git a/docs/schemas/icp-yaml-schema.json b/docs/schemas/icp-yaml-schema.json index 52b5ef2c..6a058f01 100644 --- a/docs/schemas/icp-yaml-schema.json +++ b/docs/schemas/icp-yaml-schema.json @@ -204,6 +204,7 @@ "compute_allocation": null, "environment_variables": null, "freezing_threshold": null, + "log_memory_limit": null, "log_visibility": null, "memory_allocation": null, "reserved_cycles_limit": null, @@ -835,6 +836,17 @@ ], "description": "Freezing threshold in seconds. Controls how long a canister can be inactive before being frozen.\nSupports duration suffixes in YAML: s, m, h, d, w (e.g. \"30d\" or \"4w\")." }, + "log_memory_limit": { + "anyOf": [ + { + "$ref": "#/$defs/MemoryAmount" + }, + { + "type": "null" + } + ], + "description": "Log memory limit in bytes (max 2 MiB). Oldest logs are purged when usage exceeds this value.\nSupports suffixes in YAML: kb, kib, mb, mib (e.g. \"2mib\" or \"256kib\"). Canister default is 4096 bytes." + }, "log_visibility": { "anyOf": [ {