Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ bump. Currently experimental: project bundling, project dependencies

# Unreleased

* fix: `icp canister logs` no longer has its output formats swapped. `--json` emitted the human-readable lines and the default emitted JSON; now `--json` emits machine-readable JSON (terminated by a newline) and the default emits the human-readable lines. `--follow` was already correct and is unchanged. Scripts that relied on the inverted behavior — parsing the default output as JSON, or omitting `--json` to get JSON — must now pass `--json`.

# v1.3.0

* feat: a canister environment variable's value can now be read from a file, by writing `var: { path: <file> }` in place of `var: value`. The path resolves against the canister's directory — including in an environment override, matching `init_args` — and surrounding whitespace is trimmed off the file's contents. The file is read when the project is loaded, so a missing file fails before anything is deployed. `icp project bundle` writes the value into the bundled manifest inline, rejecting a file outside the project as it does for other manifest file references.
Expand Down
27 changes: 16 additions & 11 deletions crates/icp-cli/src/commands/canister/logs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::io::stdout;
use std::io::{Write as _, stdout};

use anyhow::{Context as _, anyhow};
use candid::Principal;
Expand Down Expand Up @@ -179,17 +179,9 @@ async fn fetch_and_display_logs(
.context("Failed to fetch canister logs")?;

if args.json {
println!(
"{}",
result
.canister_log_records
.iter()
.map(format_log)
.format("\n")
);
} else {
let mut out = stdout();
serde_json::to_writer(
stdout(),
&mut out,
&JsonListRecord {
log_records: result
.canister_log_records
Expand All @@ -202,6 +194,19 @@ async fn fetch_and_display_logs(
.collect(),
},
)?;
// Unlike the other `--json` sites in this module, terminate the document with a
// newline: it is the entirety of stdout, so without it the shell prompt lands
// mid-line. Covered by `canister_logs_single_fetch`.
writeln!(out)?;
} else {
println!(
"{}",
result
.canister_log_records
.iter()
.map(format_log)
.format("\n")
);
}

Ok(())
Expand Down
44 changes: 41 additions & 3 deletions crates/icp-cli/tests/canister_logs_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ async fn canister_logs_single_fetch() {
.assert()
.success();

// Fetch logs
// Fetch logs: the default emits the human-readable "[idx. timestamp]: content" lines,
// not JSON. (Regression: this orientation was once swapped with `--json`.)
ctx.icp()
.current_dir(&project_dir)
.args([
Expand All @@ -86,8 +87,45 @@ async fn canister_logs_single_fetch() {
])
.assert()
.success()
.stdout(contains("Test message 1"))
.stdout(contains("Test message 2"));
.stdout(contains("]: Test message 1"))
.stdout(contains("]: Test message 2"))
.stdout(contains("log_records").not());

// With --json: parseable JSON in the JsonListRecord shape, terminated by a newline.
let json_stdout = ctx
.icp()
.current_dir(&project_dir)
.args([
"canister",
"logs",
"logger",
"--json",
"--environment",
"random-environment",
])
.assert()
.success()
.get_output()
.stdout
.clone();
let json_stdout = String::from_utf8(json_stdout).expect("stdout is not valid UTF-8");
assert!(
json_stdout.ends_with('\n'),
"--json output should end with a newline, got {json_stdout:?}"
);
let parsed: serde_json::Value =
serde_json::from_str(json_stdout.trim()).expect("--json output should be valid JSON");
let records = parsed["log_records"]
.as_array()
.expect("--json output should contain a log_records array");
for message in ["Test message 1", "Test message 2"] {
let record = records
.iter()
.find(|r| r["content"].as_str() == Some(message))
.unwrap_or_else(|| panic!("--json output should contain {message:?}"));
assert!(record["timestamp"].is_u64());
assert!(record["index"].is_u64());
}
}

#[cfg(unix)] // moc
Expand Down
Loading