From adcdcaa1e8e612046bb98b3f5256f467ae397c97 Mon Sep 17 00:00:00 2001 From: Matt Gabrenya Date: Mon, 13 Oct 2025 16:08:55 -0700 Subject: [PATCH 1/6] feat: display 'Player #' in logs to distinguish log messages from different conductors --- CHANGELOG.md | 3 +++ ts/src/conductor.ts | 49 +++++++++++++++++++++++++++------------------ ts/src/scenario.ts | 19 ++++++++++-------- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7a9f5d9..08f9a6e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - A new method `storageArc` that polls for network metrics and to check if an agent's storage arc is equal to a desired storage arc for a given dna hash. ([#300](https://github.com/holochain/tryorama/pull/301)) - A new configuration field `targetArcFactor` has been added to `NetworkConfig`, allowing test scenarios to be written with 0-arc conductors ([#300](https://github.com/holochain/tryorama/pull/300)) +- A new optional field `label` was added to the types `AppWithOptions`, `ConductorOptions`, and `CreateConductorOptions`. It was also added to the function `addConductor`. It allowing overriding the default label which will prefix log messages of that Conductor instance. ### Removed ### Changed +- Log messages from Conductors that were created with `addPlayers` functions are now prefixed by `Tryorama - Player #` where "#" is the Player index. This makes it easier to distinguish log messages from different conductors. ([#302](https://github.com/holochain/tryorama/pull/302)) + ### Fixed - Network options are now applied before the conductor launches, to ensure they actually alter the conductor configuration. ([#300](https://github.com/holochain/tryorama/pull/300)) diff --git a/ts/src/conductor.ts b/ts/src/conductor.ts index d8556038..24cc868f 100644 --- a/ts/src/conductor.ts +++ b/ts/src/conductor.ts @@ -21,8 +21,9 @@ import { _ALLOWED_ORIGIN } from "./conductor-helpers.js"; import { makeLogger } from "./logger.js"; import { AppWithOptions } from "./scenario.js"; import { AgentsAppsOptions } from "./types.js"; +import { Logger } from "winston"; -const logger = makeLogger(); +const defaultLogger = makeLogger(); /** * @public @@ -52,6 +53,11 @@ export interface ConductorOptions { * Timeout for requests to Admin and App API. */ timeout?: number; + + /** + * Label to identify this conductor in logs. + */ + label?: string; } /** @@ -162,7 +168,7 @@ interface ConductorConfigYaml { */ export type CreateConductorOptions = Pick< ConductorOptions, - "bootstrapServerUrl" | "timeout" + "bootstrapServerUrl" | "timeout" | "label" >; /** @@ -180,6 +186,7 @@ export const createConductor = async ( const createConductorOptions: CreateConductorOptions = pick(options, [ "bootstrapServerUrl", "timeout", + "label" ]); const conductor = await Conductor.create( signalingServerUrl, @@ -211,14 +218,16 @@ export class Conductor { private adminApiUrl: URL; private _adminWs: AdminWebsocket | undefined; private _appWs: AppWebsocket | undefined; + private _logger: Logger; private readonly timeout: number; - private constructor(timeout?: number) { + private constructor(label?: string, timeout?: number) { this.conductorProcess = undefined; this.conductorDir = undefined; this.adminApiUrl = new URL(HOST_URL.href); this._adminWs = undefined; this._appWs = undefined; + this._logger = makeLogger(label); this.timeout = timeout ?? DEFAULT_TIMEOUT; } @@ -238,15 +247,15 @@ export class Conductor { } args.push("webrtc"); args.push(signalingServerUrl.href); - logger.debug("spawning hc sandbox with args:", args); + defaultLogger.debug("spawning hc sandbox with args:", args); const createConductorProcess = spawn("hc", args); createConductorProcess.stdin.write(LAIR_PASSWORD); createConductorProcess.stdin.end(); - const conductor = new Conductor(options?.timeout); + const conductor = new Conductor(options?.label, options?.timeout); return new Promise((resolve, reject) => { createConductorProcess.stdout.on("data", (data: Buffer) => { - logger.debug(`creating conductor config\n${data.toString()}`); + defaultLogger.debug(`creating conductor config\n${data.toString()}`); const tmpDirMatches = [ ...data.toString().matchAll(/DataRootPath\("(.*?)"\)/g), ]; @@ -258,7 +267,7 @@ export class Conductor { resolve(conductor); }); createConductorProcess.stderr.on("data", (err) => { - logger.error(`error when creating conductor config: ${err}\n`); + defaultLogger.error(`error when creating conductor config: ${err}\n`); reject(err); }); }); @@ -298,8 +307,8 @@ export class Conductor { }, }; const yamlDump = yaml.dump(conductorConfigYaml); - logger.debug("Updated conductor config:"); - logger.debug(yamlDump); + this._logger.debug("Updated conductor config:"); + this._logger.debug(yamlDump); writeFileSync(`${this.conductorDir}/${CONDUCTOR_CONFIG}`, yamlDump); } @@ -313,7 +322,7 @@ export class Conductor { "error starting conductor: conductor has not been created", ); if (this.conductorProcess) { - logger.error("error starting conductor: conductor is already running\n"); + this._logger.error("error starting conductor: conductor is already running\n"); return; } @@ -327,7 +336,7 @@ export class Conductor { const startPromise = new Promise((resolve) => { runConductorProcess.stdout.on("data", (data: Buffer) => { - logger.info(data.toString()); + this._logger.info(data.toString()); const conductorLaunched = data.toString().match(/Conductor ready\./); if (conductorLaunched) { // This is the last output of the startup process. @@ -340,7 +349,7 @@ export class Conductor { }); runConductorProcess.stderr.on("data", (data: Buffer) => { - logger.error(data.toString()); + this._logger.error(data.toString()); }); }); await startPromise; @@ -352,11 +361,11 @@ export class Conductor { */ async shutDown() { if (!this.conductorProcess) { - logger.info("shut down conductor: conductor is not running"); + this._logger.info("shut down conductor: conductor is not running"); return null; } - logger.debug("closing admin and app web sockets\n"); + this._logger.debug("closing admin and app web sockets\n"); if (this._adminWs) { await this._adminWs.client.close(); this._adminWs = undefined; @@ -366,7 +375,7 @@ export class Conductor { this._appWs = undefined; } - logger.debug("shutting down conductor\n"); + this._logger.debug("shutting down conductor\n"); return new Promise((resolve) => { assert(this.conductorProcess); // Kill process after timeout if terminating didn't succeed. @@ -391,7 +400,7 @@ export class Conductor { wsClientOptions: { origin: _ALLOWED_ORIGIN }, defaultTimeout: this.timeout, }); - logger.debug(`connected to Admin API @ ${this.adminApiUrl.href}\n`); + this._logger.debug(`connected to Admin API @ ${this.adminApiUrl.href}\n`); } /** @@ -405,7 +414,7 @@ export class Conductor { port: await getPort({ port: portNumbers(30000, 40000) }), allowed_origins: _ALLOWED_ORIGIN, }; - logger.debug(`attaching App API to port ${request.port}\n`); + this._logger.debug(`attaching App API to port ${request.port}\n`); const { port } = await this.adminWs().attachAppInterface(request); return port; } @@ -418,7 +427,7 @@ export class Conductor { * @returns An app websocket. */ async connectAppWs(token: AppAuthenticationToken, port: number) { - logger.debug(`connecting App WebSocket to port ${port}\n`); + this._logger.debug(`connecting App WebSocket to port ${port}\n`); const appApiUrl = new URL(this.adminApiUrl.href); appApiUrl.port = port.toString(); const appWs = await AppWebsocket.connect({ @@ -496,7 +505,7 @@ export class Conductor { installed_app_id, network_seed, }; - logger.debug( + this._logger.debug( `installing app with id ${installed_app_id} for agent ${encodeHashToBase64( agent_key, )}`, @@ -525,7 +534,7 @@ export const cleanAllConductors = async () => { const conductorProcess = spawn("hc", ["sandbox", "clean"]); return new Promise((resolve) => { conductorProcess.stdout.once("end", () => { - logger.debug("sandbox conductors cleaned\n"); + defaultLogger.debug("sandbox conductors cleaned\n"); resolve(); }); }); diff --git a/ts/src/scenario.ts b/ts/src/scenario.ts index 92da135f..66af5b2d 100644 --- a/ts/src/scenario.ts +++ b/ts/src/scenario.ts @@ -43,6 +43,7 @@ export interface PlayerApp extends Player, AgentApp { export interface AppWithOptions { appBundleSource: AppBundleSource; options?: AppOptions; + label?: string; } /** @@ -101,13 +102,14 @@ export class Scenario { * * @returns The newly added conductor instance. */ - async addConductor(networkConfig?: NetworkConfig) { + async addConductor(networkConfig?: NetworkConfig, label?: string) { await this.ensureLocalServices(); assert(this.serviceProcess); assert(this.signalingServerUrl); const defaultCreateOptions = { timeout: this.timeout, bootstrapServerUrl: this.bootstrapServerUrl, + label, }; const createOptions = networkConfig === undefined @@ -140,11 +142,8 @@ export class Scenario { ): Promise { await this.ensureLocalServices(); return Promise.all( - new Array(amount).fill(0).map(async () => { - const conductor = await this.addConductor(); - if (networkConfig) { - conductor.setNetworkConfig(networkConfig); - } + new Array(amount).fill(0).map(async (i) => { + const conductor = await this.addConductor(networkConfig, `Player ${i}`); const agentPubKey = await conductor.adminWs().generateAgentPubKey(); return { conductor, agentPubKey }; }), @@ -226,6 +225,7 @@ export class Scenario { await this.ensureLocalServices(); const conductor = await this.addConductor( appWithOptions.options?.networkConfig, + appWithOptions.label, ); appWithOptions.options = { ...appWithOptions.options, @@ -279,8 +279,11 @@ export class Scenario { async addPlayersWithApps(appsWithOptions: AppWithOptions[]) { await this.ensureLocalServices(); return Promise.all( - appsWithOptions.map((appWithOptions) => - this.addPlayerWithApp(appWithOptions), + appsWithOptions.map((appWithOptions, i) => + this.addPlayerWithApp({ + label: `Player ${i}`, // default label + ...appWithOptions + }), ), ); } From fc8acc7607b5cce68ae640fb0c4b865c1dc3bf48 Mon Sep 17 00:00:00 2001 From: Matt Gabrenya Date: Mon, 13 Oct 2025 16:30:57 -0700 Subject: [PATCH 2/6] feat: display 'Player #' in logs to distinguish log messages from different conductors --- ts/src/conductor.ts | 6 ++++-- ts/src/scenario.ts | 13 ++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ts/src/conductor.ts b/ts/src/conductor.ts index 24cc868f..2f0db16e 100644 --- a/ts/src/conductor.ts +++ b/ts/src/conductor.ts @@ -186,7 +186,7 @@ export const createConductor = async ( const createConductorOptions: CreateConductorOptions = pick(options, [ "bootstrapServerUrl", "timeout", - "label" + "label", ]); const conductor = await Conductor.create( signalingServerUrl, @@ -322,7 +322,9 @@ export class Conductor { "error starting conductor: conductor has not been created", ); if (this.conductorProcess) { - this._logger.error("error starting conductor: conductor is already running\n"); + this._logger.error( + "error starting conductor: conductor is already running\n", + ); return; } diff --git a/ts/src/scenario.ts b/ts/src/scenario.ts index 66af5b2d..90a65929 100644 --- a/ts/src/scenario.ts +++ b/ts/src/scenario.ts @@ -142,7 +142,7 @@ export class Scenario { ): Promise { await this.ensureLocalServices(); return Promise.all( - new Array(amount).fill(0).map(async (i) => { + new Array(amount).fill(0).map(async (_, i) => { const conductor = await this.addConductor(networkConfig, `Player ${i}`); const agentPubKey = await conductor.adminWs().generateAgentPubKey(); return { conductor, agentPubKey }; @@ -263,9 +263,12 @@ export class Scenario { async addPlayersWithSameApp(appWithOptions: AppWithOptions, amount: number) { await this.ensureLocalServices(); return Promise.all( - new Array(amount) - .fill(0) - .map(() => this.addPlayerWithApp(appWithOptions)), + new Array(amount).fill(0).map((_, i) => + this.addPlayerWithApp({ + label: `Player ${i}`, // default label + ...appWithOptions, + }), + ), ); } @@ -282,7 +285,7 @@ export class Scenario { appsWithOptions.map((appWithOptions, i) => this.addPlayerWithApp({ label: `Player ${i}`, // default label - ...appWithOptions + ...appWithOptions, }), ), ); From 305ec516528ede711b8ea51a61108c27c3d312ca Mon Sep 17 00:00:00 2001 From: Matt Gabrenya Date: Mon, 13 Oct 2025 16:32:57 -0700 Subject: [PATCH 3/6] docs: rebuild docs --- docs/tryorama.appwithoptions.label.md | 11 +++++++++++ docs/tryorama.appwithoptions.md | 19 +++++++++++++++++++ docs/tryorama.conductoroptions.label.md | 13 +++++++++++++ docs/tryorama.conductoroptions.md | 19 +++++++++++++++++++ docs/tryorama.createconductoroptions.md | 2 +- docs/tryorama.scenario.addconductor.md | 18 +++++++++++++++++- docs/tryorama.scenario.md | 2 +- 7 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 docs/tryorama.appwithoptions.label.md create mode 100644 docs/tryorama.conductoroptions.label.md diff --git a/docs/tryorama.appwithoptions.label.md b/docs/tryorama.appwithoptions.label.md new file mode 100644 index 00000000..f9ba98d7 --- /dev/null +++ b/docs/tryorama.appwithoptions.label.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@holochain/tryorama](./tryorama.md) > [AppWithOptions](./tryorama.appwithoptions.md) > [label](./tryorama.appwithoptions.label.md) + +## AppWithOptions.label property + +**Signature:** + +```typescript +label?: string; +``` diff --git a/docs/tryorama.appwithoptions.md b/docs/tryorama.appwithoptions.md index 4fb96ead..330be1d9 100644 --- a/docs/tryorama.appwithoptions.md +++ b/docs/tryorama.appwithoptions.md @@ -50,6 +50,25 @@ AppBundleSource + + + +[label?](./tryorama.appwithoptions.label.md) + + + + + + + +string + + + + +_(Optional)_ + + diff --git a/docs/tryorama.conductoroptions.label.md b/docs/tryorama.conductoroptions.label.md new file mode 100644 index 00000000..74b03e82 --- /dev/null +++ b/docs/tryorama.conductoroptions.label.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@holochain/tryorama](./tryorama.md) > [ConductorOptions](./tryorama.conductoroptions.md) > [label](./tryorama.conductoroptions.label.md) + +## ConductorOptions.label property + +Label to identify this conductor in logs. + +**Signature:** + +```typescript +label?: string; +``` diff --git a/docs/tryorama.conductoroptions.md b/docs/tryorama.conductoroptions.md index ac8d6bcd..18ada00a 100644 --- a/docs/tryorama.conductoroptions.md +++ b/docs/tryorama.conductoroptions.md @@ -52,6 +52,25 @@ URL _(Optional)_ A bootstrap server URL for peers to discover each other. + + + +[label?](./tryorama.conductoroptions.label.md) + + + + + + + +string + + + + +_(Optional)_ Label to identify this conductor in logs. + + diff --git a/docs/tryorama.createconductoroptions.md b/docs/tryorama.createconductoroptions.md index 2d24cfe9..4648cdf8 100644 --- a/docs/tryorama.createconductoroptions.md +++ b/docs/tryorama.createconductoroptions.md @@ -9,7 +9,7 @@ Options for using the conductor factory. **Signature:** ```typescript -export type CreateConductorOptions = Pick; +export type CreateConductorOptions = Pick; ``` **References:** [ConductorOptions](./tryorama.conductoroptions.md) diff --git a/docs/tryorama.scenario.addconductor.md b/docs/tryorama.scenario.addconductor.md index 737a4600..52bdaf68 100644 --- a/docs/tryorama.scenario.addconductor.md +++ b/docs/tryorama.scenario.addconductor.md @@ -9,7 +9,7 @@ Create and add a conductor to the scenario. **Signature:** ```typescript -addConductor(networkConfig?: NetworkConfig): Promise; +addConductor(networkConfig?: NetworkConfig, label?: string): Promise; ``` ## Parameters @@ -45,6 +45,22 @@ networkConfig _(Optional)_ + + + +label + + + + +string + + + + +_(Optional)_ + + **Returns:** diff --git a/docs/tryorama.scenario.md b/docs/tryorama.scenario.md index 3d20f160..7a26d73a 100644 --- a/docs/tryorama.scenario.md +++ b/docs/tryorama.scenario.md @@ -227,7 +227,7 @@ Description -[addConductor(networkConfig)](./tryorama.scenario.addconductor.md) +[addConductor(networkConfig, label)](./tryorama.scenario.addconductor.md) From 18842b5fb9048a773cba9a9e2d77a21888115fce Mon Sep 17 00:00:00 2001 From: Matt Gabrenya Date: Tue, 14 Oct 2025 09:30:55 -0700 Subject: [PATCH 4/6] chore: grammer --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08f9a6e2..1bd66b89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - A new method `storageArc` that polls for network metrics and to check if an agent's storage arc is equal to a desired storage arc for a given dna hash. ([#300](https://github.com/holochain/tryorama/pull/301)) - A new configuration field `targetArcFactor` has been added to `NetworkConfig`, allowing test scenarios to be written with 0-arc conductors ([#300](https://github.com/holochain/tryorama/pull/300)) -- A new optional field `label` was added to the types `AppWithOptions`, `ConductorOptions`, and `CreateConductorOptions`. It was also added to the function `addConductor`. It allowing overriding the default label which will prefix log messages of that Conductor instance. +- A new optional field `label` was added to the types `AppWithOptions`, `ConductorOptions`, `CreateConductorOptions`, and to the function `addConductor`. It allows overriding the default label which will prefix log messages of that Conductor instance. ### Removed ### Changed From 56237d08c77f3c996a81bdf8c3ac87e0379b4fac Mon Sep 17 00:00:00 2001 From: Matt Gabrenya Date: Tue, 14 Oct 2025 11:08:46 -0700 Subject: [PATCH 5/6] fix: if addPlayersX is called multiple times, ensure that subsequently created players' label is incremented starting from number of existing players --- ts/src/scenario.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ts/src/scenario.ts b/ts/src/scenario.ts index 90a65929..0f05562b 100644 --- a/ts/src/scenario.ts +++ b/ts/src/scenario.ts @@ -143,7 +143,7 @@ export class Scenario { await this.ensureLocalServices(); return Promise.all( new Array(amount).fill(0).map(async (_, i) => { - const conductor = await this.addConductor(networkConfig, `Player ${i}`); + const conductor = await this.addConductor(networkConfig, this.generatePlayerLabel(i)); const agentPubKey = await conductor.adminWs().generateAgentPubKey(); return { conductor, agentPubKey }; }), @@ -265,7 +265,7 @@ export class Scenario { return Promise.all( new Array(amount).fill(0).map((_, i) => this.addPlayerWithApp({ - label: `Player ${i}`, // default label + label: this.generatePlayerLabel(i), ...appWithOptions, }), ), @@ -284,7 +284,7 @@ export class Scenario { return Promise.all( appsWithOptions.map((appWithOptions, i) => this.addPlayerWithApp({ - label: `Player ${i}`, // default label + label: this.generatePlayerLabel(i), ...appWithOptions, }), ), @@ -337,6 +337,10 @@ export class Scenario { } = await runLocalServices()); } } + + private generatePlayerLabel(index: number): string { + return `Player ${this.conductors.length + index}`; + } } /** From 25d4f58465fad6a7682ab17f7ab0bfa585bf85ab Mon Sep 17 00:00:00 2001 From: Matt Gabrenya Date: Tue, 14 Oct 2025 12:59:56 -0700 Subject: [PATCH 6/6] chore: lint --- ts/src/scenario.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ts/src/scenario.ts b/ts/src/scenario.ts index 0f05562b..4037aafc 100644 --- a/ts/src/scenario.ts +++ b/ts/src/scenario.ts @@ -143,7 +143,10 @@ export class Scenario { await this.ensureLocalServices(); return Promise.all( new Array(amount).fill(0).map(async (_, i) => { - const conductor = await this.addConductor(networkConfig, this.generatePlayerLabel(i)); + const conductor = await this.addConductor( + networkConfig, + this.generatePlayerLabel(i), + ); const agentPubKey = await conductor.adminWs().generateAgentPubKey(); return { conductor, agentPubKey }; }),