Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Download logs #222

Merged
merged 7 commits into from
Jul 17, 2024
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
25 changes: 25 additions & 0 deletions crates/trycp_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ pub enum Request {
#[serde(with = "serde_bytes")]
message: Vec<u8>,
},

/// Request the logs for a player's conductor and keystore.
DownloadLogs {
/// The conductor id.
id: String,
},
}

/// Message response types.
Expand Down Expand Up @@ -153,3 +159,22 @@ pub enum MessageToClient {
response: std::result::Result<MessageResponse, String>,
},
}

/// Messages returned directly by the TryCp server, rather than relayed from Holochain
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case", tag = "type", content = "data")]
pub enum TryCpServerResponse {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some type f*ry required to let the JS client decode these types. It's the first time we've returned anything more complicated than a string and the JS client has a special case for strings.

/// See [DownloadLogsResponse].
DownloadLogs(DownloadLogsResponse),
}

/// The successful response type for a [Request::DownloadLogs] request.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct DownloadLogsResponse {
/// The lair keystore stderr log.
pub lair_stderr: Vec<u8>,
/// The holochain conductor stdout log.
pub conductor_stdout: Vec<u8>,
/// The holochain conductor stderr log.
pub conductor_stderr: Vec<u8>,
}
55 changes: 55 additions & 0 deletions crates/trycp_server/src/download_logs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use crate::{
get_player_dir, player_config_exists, CONDUCTOR_STDERR_LOG_FILENAME,
CONDUCTOR_STDOUT_LOG_FILENAME, LAIR_STDERR_LOG_FILENAME,
};
use snafu::{ResultExt, Snafu};
use trycp_api::{DownloadLogsResponse, MessageResponse, TryCpServerResponse};

#[derive(Debug, Snafu)]
pub(crate) enum DownloadLogsError {
#[snafu(display("No player with this ID is configured {}", id))]
PlayerNotConfigured { id: String },
#[snafu(display("Could not read lair stderr log for player with ID {}: {}", id, source))]
LairStdErr { id: String, source: std::io::Error },
#[snafu(display(
"Could not read holochain stdout log for player with ID {}: {}",
id,
source
))]
HolochainStdout { id: String, source: std::io::Error },
#[snafu(display(
"Could not read holochain stderr log for player with ID {}: {}",
id,
source
))]
HolochainStderr { id: String, source: std::io::Error },
#[snafu(display("Could not serialize response: {}", source))]
SerializeResponse { source: rmp_serde::encode::Error },
}

pub(crate) fn download_logs(id: String) -> Result<MessageResponse, DownloadLogsError> {
if !player_config_exists(&id) {
return Err(DownloadLogsError::PlayerNotConfigured { id });
}

let player_dir = get_player_dir(&id);

let lair_stderr = player_dir.join(LAIR_STDERR_LOG_FILENAME);
let lair_stderr = std::fs::read(&lair_stderr).context(LairStdErr { id: id.clone() })?;

let conductor_stdout = player_dir.join(CONDUCTOR_STDOUT_LOG_FILENAME);
let conductor_stdout =
std::fs::read(&conductor_stdout).context(HolochainStdout { id: id.clone() })?;

let conductor_stderr = player_dir.join(CONDUCTOR_STDERR_LOG_FILENAME);
let conductor_stderr = std::fs::read(&conductor_stderr).context(HolochainStderr { id })?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we cool with these blocking whatever thread happens to be running?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entire function is being run on a blocking thread -> https://github.com/holochain/tryorama/pull/222/files#diff-db8eb8bbb5e02b33e796b281bce321e0d781f68204bac8f217e5eddbe9e34319R256

This seems to be the established pattern for the trycp_server, to offload the entire function if it's not async


Ok(MessageResponse::Bytes(
rmp_serde::to_vec_named(&TryCpServerResponse::DownloadLogs(DownloadLogsResponse {
lair_stderr,
conductor_stdout,
conductor_stderr,
}))
.context(SerializeResponse)?,
))
}
9 changes: 9 additions & 0 deletions crates/trycp_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod admin_call;
mod app_interface;
mod configure_player;
mod download_dna;
mod download_logs;
mod reset;
mod save_dna;
mod shutdown;
Expand Down Expand Up @@ -252,6 +253,14 @@ async fn ws_message(
Err(e) => serialize_resp(request_id, Err::<(), _>(e.to_string())),
}
}
Request::DownloadLogs { id } => spawn_blocking(move || {
serialize_resp(
request_id,
download_logs::download_logs(id).map_err(|e| e.to_string()),
)
})
.await
.unwrap(),
};

Ok(Some(Message::Binary(response)))
Expand Down
4 changes: 4 additions & 0 deletions crates/trycp_server/src/startup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ pub fn startup(id: String, log_level: Option<String>) -> Result<(), Error> {
.arg("--piped")
.arg("-c")
.arg(CONDUCTOR_CONFIG_FILENAME)
// Disable ANSI color codes in Holochain output, which should be set any time the output
// is being written to a file.
// See https://docs.rs/tracing-subscriber/0.3.18/tracing_subscriber/fmt/struct.Layer.html#method.with_ansi
.env("NO_COLOR", "1")
.env("RUST_BACKTRACE", "full")
.env("RUST_LOG", rust_log)
.stdin(Stdio::piped())
Expand Down
13 changes: 13 additions & 0 deletions ts/src/trycp/conductor/conductor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { makeLogger } from "../../logger.js";
import { AgentsAppsOptions, AppOptions, IConductor } from "../../types.js";
import {
AdminApiResponseAppAuthenticationTokenIssued,
DownloadLogsResponse,
TryCpClient,
TryCpConductorLogLevel,
} from "../index.js";
Expand Down Expand Up @@ -312,6 +313,18 @@ export class TryCpConductor implements IConductor {
return response;
}

async downloadLogs() {
const response = await this.tryCpClient.call({
type: "download_logs",
id: this.id,
});
assert(response !== TRYCP_SUCCESS_RESPONSE);
assert(typeof response === "object");
assert("type" in response);
assert(response.type === "download_logs");
return response.data;
}

/**
* Attach a signal handler.
*
Expand Down
22 changes: 21 additions & 1 deletion ts/src/trycp/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ export type TryCpRequest =
| RequestDisconnectAppInterface
| RequestCallAppInterface
| RequestCallAppInterfaceEncoded
| RequestCallAdminInterface;
| RequestCallAdminInterface
| RequestDownloadLogs;

/**
* Request to download a DNA from a URL.
Expand Down Expand Up @@ -249,8 +250,22 @@ export type TryCpResponseErrorValue = string | Error;
export type TryCpApiResponse =
| AdminApiResponse
| AppApiResponse
| TryCpControlResponse
| ApiErrorResponse;

export type TryCpControlResponse = DownloadLogsResponseType;

export interface DownloadLogsResponseType {
type: "download_logs";
data: DownloadLogsResponse;
}

export interface DownloadLogsResponse {
lair_stderr: Uint8Array;
conductor_stdout: Uint8Array;
conductor_stderr: Uint8Array;
}

/**
* Error response from the Admin or App API.
*
Expand Down Expand Up @@ -479,6 +494,11 @@ export interface RequestCallAdminInterface {
message: RequestAdminInterfaceMessage;
}

export interface RequestDownloadLogs {
type: "download_logs";
id: ConductorId;
}

/**
* The types of all possible calls to the Admin API.
*
Expand Down
17 changes: 17 additions & 0 deletions ts/test/trycp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,3 +314,20 @@ test("TryCP Server - App API - get app info", async (t) => {
await conductor.disconnectClient();
await localTryCpServer.stop();
});

test("TryCP Client - download logs", async (t) => {
const localTryCpServer = await TryCpServer.start();
const tryCpClient = await createTryCpClient();
const conductor = await createTryCpConductor(tryCpClient);
const logs = await conductor.downloadLogs();

t.true(logs.lair_stderr.length === 0, "lair stderr logs are empty");
t.true(
logs.conductor_stdout.length > 0,
"conductor stdout logs are not empty"
);
t.true(logs.conductor_stderr.length === 0, "conductor stderr logs are empty");

await conductor.shutDown();
await localTryCpServer.stop();
});
Loading