diff --git a/docs/building-apps/canister-management/settings.mdx b/docs/building-apps/canister-management/settings.mdx index 5ffe29945e..071c64d5f3 100644 --- a/docs/building-apps/canister-management/settings.mdx +++ b/docs/building-apps/canister-management/settings.mdx @@ -13,7 +13,7 @@ import { GlossaryTooltip } from "/src/components/Tooltip/GlossaryTooltip"; Each canister has a group of possible settings that control its behavior. Only a controller of the canister can read and modify a canister's settings. -Currently, there are nine canister setting fields: +Currently, there are ten canister settings fields: 1. `controllers`: The list of controllers of the canister. 2. `compute_allocation`: Amount of compute that the canister has allocated. @@ -22,8 +22,9 @@ Currently, there are nine canister setting fields: 5. `reserved_cycles_limit`: A safety threshold to protect against spending too many cycles for resource reservation. 6. `wasm_memory_limit`: A safety threshold to protect against reaching the 4GiB hard limit of 32-bit Wasm memory. 7. `log_visibility`: Controls who can read the canister logs. -8. `wasm_memory_threshold`: A threshold that triggers the "on low wasm memory" hook when the canister's remaining wasm memory falls below the provided value. -9. `environment_variables`: A set of key-value pairs that configure canister behavior without code changes. +8. `log_memory_limit`: The maximum amount of memory used for canister logs. +9. `wasm_memory_threshold`: A threshold that triggers the "on low wasm memory" hook when the canister's remaining wasm memory falls below the provided value. +10. `environment_variables`: A set of key-value pairs that configure canister behavior without code changes. ## Viewing current settings diff --git a/docs/references/_attachments/ic.did b/docs/references/_attachments/ic.did index cad1d2b46d..7151e5ce2f 100644 --- a/docs/references/_attachments/ic.did +++ b/docs/references/_attachments/ic.did @@ -26,6 +26,7 @@ type canister_settings = record { freezing_threshold : opt nat; reserved_cycles_limit : opt nat; log_visibility : opt log_visibility; + log_memory_limit : opt nat; snapshot_visibility : opt snapshot_visibility; wasm_memory_limit : opt nat; wasm_memory_threshold : opt nat; @@ -39,6 +40,7 @@ type definite_canister_settings = record { freezing_threshold : nat; reserved_cycles_limit : nat; log_visibility : log_visibility; + log_memory_limit : nat; snapshot_visibility : snapshot_visibility; wasm_memory_limit : nat; wasm_memory_threshold : nat; @@ -300,6 +302,7 @@ type canister_status_result = record { canister_history_size : nat; wasm_chunk_store_size : nat; snapshots_size : nat; + log_memory_store_size : nat; }; cycles : nat; reserved_cycles : nat; diff --git a/docs/references/ic-interface-spec.md b/docs/references/ic-interface-spec.md index 7e807aff5f..041ca9f318 100644 --- a/docs/references/ic-interface-spec.md +++ b/docs/references/ic-interface-spec.md @@ -2505,6 +2505,13 @@ The optional `settings` parameter can be used to set the following settings: Default value: `controllers`. +- `log_memory_limit` (`nat`) + + Must be at most `2097152` (`2 MiB`) and indicates the maximum amount of memory used for canister logs. + Oldest canister logs are purged if the total memory used for canister logs exceeds this value. + + Default value: `4096`. + - `snapshot_visibility` (`snapshot_visibility`) Controls who can access the canister's snapshots through the following endpoints of the management canister: @@ -2710,6 +2717,8 @@ Only the controllers of the canister or the canister itself or subnet admins can * `snapshots_size`: Represents the memory consumed by all snapshots that belong to this canister. + * `log_memory_store_size`: Represents the memory used by canister logs of the canister. + All sizes are expressed in bytes. ### IC method `canister_info` {#ic-canister_info} @@ -3311,12 +3320,12 @@ A snapshot may be deleted only by the controllers of the canister that the snaps ### IC method `fetch_canister_logs` {#ic-fetch_canister_logs} -This method can only be called by external users via non-replicated (query) calls, i.e., it cannot be called by canisters, cannot be called via replicated calls, and cannot be called from composite query calls. +This method can only be called by external users via non-replicated (query) calls or by canisters (via replicated calls), i.e., it cannot be called by external users via replicated (update) calls and it cannot be called from composite query calls. Given a canister ID as input, this method returns a vector of logs of that canister including its trap messages. The canister logs are *not* collected in canister methods running in non-replicated mode (NRQ, TQ, CQ, CRy, CRt, CC, and F modes, as defined in [Overview of imports](#system-api-imports)) and the canister logs are *purged* when the canister is reinstalled or uninstalled. -The total size of all returned logs does not exceed 4KiB. -If new logs are added resulting in exceeding the maximum total log size of 4KiB, the oldest logs will be removed. +The total size of all returned logs does not exceed the value `log_memory_limit` in canister settings. +Oldest canister logs are purged if the total memory used for canister logs exceeds the value `log_memory_limit` in canister settings. Logs persist across canister upgrades and they are deleted if the canister is reinstalled or uninstalled. The log visibility is defined in the `log_visibility` field of `canister_settings` and can be one of the following variants: @@ -4258,6 +4267,7 @@ S = { certified_data: CanisterId ↦ Blob; canister_history: CanisterId ↦ CanisterHistory; canister_log_visibility: CanisterId ↦ CanisterLogVisibility; + canister_log_memory_limit: CanisterId ↦ Nat; canister_snapshot_visibility: CanisterId ↦ CanisterSnapshotVisibility; canister_logs: CanisterId ↦ [CanisterLog]; query_stats: CanisterId ↦ [QueryStats]; @@ -4367,6 +4377,7 @@ The initial state of the IC is certified_data = (); canister_history = (); canister_log_visibility = (); + canister_log_memory_limit = (); canister_snapshot_visibility = (); canister_logs = (); query_stats = (); @@ -5453,6 +5464,11 @@ if A.settings.log_visibility is not null: else: New_canister_log_visibility = Controllers +if A.settings.log_memory_limit is not null: + New_canister_log_memory_limit = A.settings.log_memory_limit +else: + New_canister_log_memory_limit = 4096 + if A.settings.snapshot_visibility is not null: New_canister_snapshot_visibility = A.settings.snapshot_visibility else: @@ -5484,6 +5500,7 @@ S' = S with query_stats[Canister_id] = [] canister_history[Canister_id] = New_canister_history canister_log_visibility[Canister_id] = New_canister_log_visibility + canister_log_memory_limit[Canister_id] = New_canister_log_memory_limit canister_snapshot_visibility[Canister_id] = New_canister_snapshot_visibility canister_logs[Canister_id] = [] messages = Older_messages · Younger_messages · @@ -5620,6 +5637,8 @@ S' = S with canister_version[A.canister_id] = S.canister_version[A.canister_id] + 1 if A.settings.log_visibility is not null: canister_log_visibility[A.canister_id] = A.settings.log_visibility + if A.settings.log_memory_limit is not null: + canister_log_memory_limit[A.canister_id] = A.settings.log_memory_limit if A.settings.snapshot_visibility is not null: canister_snapshot_visibility[A.canister_id] = A.settings.snapshot_visibility messages = Older_messages · Younger_messages · @@ -6575,7 +6594,8 @@ S with certified_data[A.canister_id] = (deleted) canister_history[A.canister_id] = (deleted) canister_log_visibility[A.canister_id] = (deleted) - canister_snapshot_visibility[A.canister_id] = (deleted) + canister_log_memory_limit[A.canister_id] = (deleted) + canister_snapshot_visibility[A.canister_id] = (deleted) canister_logs[A.canister_id] = (deleted) query_stats[A.canister_id] = (deleted) chunk_store[A.canister_id] = (deleted) @@ -6808,6 +6828,11 @@ if A.settings.log_visibility is not null: else: New_canister_log_visibility = Controllers +if A.settings.log_memory_limit is not null: + New_canister_log_memory_limit = A.settings.log_memory_limit +else: + New_canister_log_memory_limit = 4096 + if A.settings.snapshot_visibility is not null: New_canister_snapshot_visibility = A.settings.snapshot_visibility else: @@ -6837,6 +6862,7 @@ S' = S with certified_data[Canister_id] = "" canister_history[Canister_id] = New_canister_history canister_log_visibility[Canister_id] = New_canister_log_visibility + canister_log_memory_limit[Canister_id] = New_canister_log_memory_limit canister_snapshot_visibility[Canister_id] = New_canister_snapshot_visibility canister_logs[Canister_id] = [] query_stats[CanisterId] = [] @@ -7478,6 +7504,101 @@ S with ``` +#### IC Management Canister: Canister logs {#ic-mgmt-canister-fetch-canister-logs} + +Given a state `S`, `Canister_id`, and `Sender`, we define + +```html + +canister_logs(S, Canister_id) = S.canister_logs[A.canister_id] +fetch_canister_logs_cost(S, Canister_id) = +is_sender_authorized(S, Canister_id, Sender) = + (S[Canister_id].canister_log_visibility = Public) + or + (S[Canister_id].canister_log_visibility = Controllers and Sender in S[Canister_id].controllers) + or + (S[Canister_id].canister_log_visibility = AllowedViewers Principals and (Sender in S[Canister_id].controllers or Sender in Principals)) + +``` + +Conditions + +```html + +S.messages = Older_messages · CallMessage M · Younger_messages +(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue) +M.callee = ic_principal +M.method_name = 'fetch_canister_logs' +M.arg = candid(A) +fetch_canister_logs_cost(S, A.canister_id) <= M.transferred_cycles +is_sender_authorized(S, A.canister_id, M.caller) + +``` + +State after + +```html + +S with + messages = Older_messages · Younger_messages · + ResponseMessage { + origin = M.origin + response = candid(canister_logs(S, A.canister_id)) + refunded_cycles = M.transferred_cycles - fetch_canister_logs_cost(S, A.canister_id) + } + +``` + +The IC method `fetch_canister_logs` can also be invoked via management canister query calls. +They are calls to `/api/v3/canister//query` +with CBOR content `Q` such that `Q.canister_id = ic_principal`. + +Submitted request to `/api/v3/canister//query` + +```html + +E : Envelope + +``` + +Conditions + +```html + +E.content = CanisterQuery Q +Q.canister_id = ic_principal +Q.method_name = 'fetch_canister_logs' +|Q.nonce| <= 32 +is_effective_canister_id(E.content, ECID) +S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id +Q.arg = candid(A) +A.canister_id ∈ verify_envelope(E, Q.sender, S.system_time) +if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed: + if not (Q.sender_info = null): + verify_signature E.sender_pubkey Q.sender_info.sig ("\x0Eic-sender-info" · Q.sender_info.info) + Q.sender_info.signer = Signing_canister_id +else: + Q.sender_info = null +is_sender_authorized(S, A.canister_id, Q.sender) + +``` + +Query response `R`: + +```html + +{status: "replied"; reply: {arg: candid(canister_logs(S, A.canister_id))}, signatures: Sigs} + +``` + +where the query `Q`, the response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state` satisfy the following: + +```html + +verify_response(Q, R, Cert) ∧ lookup(["time"], Cert) = Found S.system_time // or "recent enough" + +``` + #### Callback invocation When an inter-canister call has been responded to, we can queue the call to the callback. @@ -7979,16 +8100,19 @@ S with ``` -#### Trimming canister logs +#### Purging canister logs + +Oldest canister logs are purged if the total memory used for canister logs exceeds the value `log_memory_limit` in canister settings. -Canister logs can be trimmed if their total length exceeds 4KiB. +The (unspecified) function `canister_log_memory_usage(logs)` models the total memory used by `logs`. Conditions ```html S.canister_logs[CanisterId] = Older_logs · Newer_logs -SUM { |l| | l <- Older_logs } > 4KiB +canister_log_memory_usage(Older_logs · Newer_logs) > S.canister_log_memory_limit[CanisterId] +canister_log_memory_usage(Newer_logs) ≤ S.canister_log_memory_limit[CanisterId] ``` @@ -8001,66 +8125,6 @@ S with ``` -#### IC Management Canister: Canister logs (query call) {#ic-mgmt-canister-fetch-canister-logs} - -This section specifies management canister query calls. -They are calls to `/api/v3/canister//query` -with CBOR content `Q` such that `Q.canister_id = ic_principal`. - -The management canister offers the method `fetch_canister_logs` -that can be called as a query call and -returns logs of a requested canister. - -Submitted request to `/api/v3/canister//query` - -```html - -E : Envelope - -``` - -Conditions - -```html - -E.content = CanisterQuery Q -Q.canister_id = ic_principal -Q.method_name = 'fetch_canister_logs' -|Q.nonce| <= 32 -is_effective_canister_id(E.content, ECID) -S.system_time <= Q.ingress_expiry or Q.sender = anonymous_id -Q.arg = candid(A) -A.canister_id ∈ verify_envelope(E, Q.sender, S.system_time) -if E.sender_pubkey = canister_signature_pk Signing_canister_id Seed: - if not (Q.sender_info = null): - verify_signature E.sender_pubkey Q.sender_info.sig ("\x0Eic-sender-info" · Q.sender_info.info) - Q.sender_info.signer = Signing_canister_id -else: - Q.sender_info = null -(S[A.canister_id].canister_log_visibility = Public) - or - (S[A.canister_id].canister_log_visibility = Controllers and Q.sender in S[A.canister_id].controllers) - or - (S[A.canister_id].canister_log_visibility = AllowedViewers Principals and (Q.sender in S[A.canister_id].controllers or Q.sender in Principals)) - -``` - -Query response `R`: - -```html - -{status: "replied"; reply: {arg: candid(S.canister_logs[A.canister_id])}, signatures: Sigs} - -``` - -where the query `Q`, the response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v3/canister//read_state` satisfy the following: - -```html - -verify_response(Q, R, Cert) ∧ lookup(["time"], Cert) = Found S.system_time // or "recent enough" - -``` - #### IC Management Canister: List canisters (query call) {#ic-mgmt-canister-list-canisters} This section specifies the `list_canisters` management canister query call.