Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
9 changes: 9 additions & 0 deletions .changeset/expose-fluid-runner-helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@fluidframework/fluid-runner": minor
"__section": other
---
Expose `createFluidRunnerContainerAndExecute` and `createFluidRunnerLogger` as `@legacy @beta`

The lower-level helpers `createFluidRunnerContainerAndExecute` and `createFluidRunnerLogger` from `@fluidframework/fluid-runner` are now part of the legacy/beta public API surface. Use `createFluidRunnerContainerAndExecute` to load a container from an ODSP snapshot and run caller-provided code against it. Use `createFluidRunnerLogger` to obtain a file-backed telemetry logger that can be passed to `createFluidRunnerContainerAndExecute`.

Check warning on line 7 in .changeset/expose-fluid-runner-helpers.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Microsoft.Acronyms] 'ODSP' has no definition. Raw Output: {"message": "[Microsoft.Acronyms] 'ODSP' has no definition.", "location": {"path": ".changeset/expose-fluid-runner-helpers.md", "range": {"start": {"line": 7, "column": 253}}}, "severity": "INFO"}

The `IFileLogger` and `IFileLoggerTelemetryOptions` types — already exported from the package — have likewise been promoted from `@internal` to `@legacy @beta` so they can be referenced by consumers of these APIs. The signatures of `createFluidRunnerLogger` and `createFluidRunnerContainerAndExecute` use the public `ITelemetryBaseLogger` type from `@fluidframework/core-interfaces`.

Check failure on line 9 in .changeset/expose-fluid-runner-helpers.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Microsoft.Dashes] Remove the spaces around ' — '. Raw Output: {"message": "[Microsoft.Dashes] Remove the spaces around ' — '.", "location": {"path": ".changeset/expose-fluid-runner-helpers.md", "range": {"start": {"line": 9, "column": 94}}}, "severity": "ERROR"}

Check failure on line 9 in .changeset/expose-fluid-runner-helpers.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Microsoft.Dashes] Remove the spaces around ' — '. Raw Output: {"message": "[Microsoft.Dashes] Remove the spaces around ' — '.", "location": {"path": ".changeset/expose-fluid-runner-helpers.md", "range": {"start": {"line": 9, "column": 58}}}, "severity": "ERROR"}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@

```ts

// @beta @legacy
export function createFluidRunnerContainerAndExecute(localOdspSnapshot: string | Uint8Array, fluidFileConverter: IFluidFileConverter, baseLogger: ITelemetryBaseLogger, options?: string, timeout?: number, disableNetworkFetch?: boolean): Promise<string>;

// @beta @legacy
export function createFluidRunnerLogger(filePath: string, options?: IFileLoggerTelemetryOptions): {
logger: ITelemetryBaseLogger;
fileLogger: IFileLogger;
};

// @beta @legacy (undocumented)
export type IExportFileResponse = IExportFileResponseSuccess | IExportFileResponseFailure;

Expand All @@ -25,6 +34,18 @@ export interface IExportFileResponseSuccess {
success: true;
}

// @beta @legacy
export interface IFileLogger extends ITelemetryBaseLogger {
close(): Promise<void>;
}

// @beta @legacy
export interface IFileLoggerTelemetryOptions {
defaultProps?: Record<string, string | number>;
eventsPerFlush?: number;
outputFormat?: OutputFormat;
}

// @beta @legacy
export interface IFluidFileConverter {
execute(container: IContainer, options?: string): Promise<string>;
Expand Down
56 changes: 41 additions & 15 deletions packages/tools/fluid-runner/src/exportFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ import {
waitContainerToCatchUp,
type ILoaderProps,
} from "@fluidframework/container-loader/internal";
import type { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
import { createLocalOdspDocumentServiceFactory } from "@fluidframework/odsp-driver/internal";
import {
type ITelemetryLoggerExt,
PerformanceEvent,
} from "@fluidframework/telemetry-utils/internal";
import { createChildLogger, PerformanceEvent } from "@fluidframework/telemetry-utils/internal";

import type { IFluidFileConverter } from "./codeLoaderBundle.js";
import { FakeUrlResolver } from "./fakeUrlResolver.js";
/* eslint-disable import-x/no-internal-modules */
import type { ITelemetryOptions } from "./logger/fileLogger.js";
import { createLogger, getTelemetryFileValidationError } from "./logger/loggerUtils.js";
import type { IFileLoggerTelemetryOptions } from "./logger/fileLogger.js";
import {
createFluidRunnerLogger,
getTelemetryFileValidationError,
} from "./logger/loggerUtils.js";
import { getArgsValidationError, getSnapshotFileContent, timeoutPromise } from "./utils.js";
/* eslint-enable import-x/no-internal-modules */

Expand Down Expand Up @@ -50,7 +51,8 @@ export interface IExportFileResponseFailure {
const clientArgsValidationError = "Client_ArgsValidationError";

/**
* Execute code on Container based on ODSP snapshot and write result to file
* Execute code on a Fluid {@link @fluidframework/container-definitions#IContainer} loaded from an ODSP snapshot
* file and write the resulting string to disk.
* @internal
*/
export async function exportFile(
Expand All @@ -59,7 +61,7 @@ export async function exportFile(
outputFile: string,
telemetryFile: string,
options?: string,
telemetryOptions?: ITelemetryOptions,
telemetryOptions?: IFileLoggerTelemetryOptions,
timeout?: number,
disableNetworkFetch?: boolean,
): Promise<IExportFileResponse> {
Expand All @@ -68,7 +70,11 @@ export async function exportFile(
const eventName = clientArgsValidationError;
return { success: false, eventName, errorMessage: telemetryArgError };
}
const { fileLogger, logger } = createLogger(telemetryFile, telemetryOptions);
const { fileLogger, logger: baseLogger } = createFluidRunnerLogger(
telemetryFile,
telemetryOptions,
);
const logger = createChildLogger({ logger: baseLogger });

try {
return await PerformanceEvent.timedExecAsync(
Expand All @@ -84,7 +90,7 @@ export async function exportFile(

fs.writeFileSync(
outputFile,
await createContainerAndExecute(
await createFluidRunnerContainerAndExecute(
getSnapshotFileContent(inputFile),
fluidFileConverter,
logger,
Expand All @@ -107,18 +113,38 @@ export async function exportFile(
}

/**
* Create the container based on an ODSP snapshot and execute code on it
* @returns result of execution
* @internal
* Create a Fluid {@link @fluidframework/container-definitions#IContainer} from an ODSP snapshot and run
* caller-provided code against it.
*
* @remarks
* The container is loaded with `opsBeforeReturn: "cached"` and {@link @fluidframework/container-loader#waitContainerToCatchUp}
* is invoked before {@link IFluidFileConverter.execute} runs. The container is disposed once `execute` resolves
* (or rejects).
*
* @param localOdspSnapshot - The ODSP snapshot to load the container from. May be either the JSON snapshot
* as a string or the binary snapshot as a `Uint8Array`.
* @param fluidFileConverter - Caller-provided code loader and execution logic. See {@link IFluidFileConverter}.
* @param baseLogger - Telemetry logger that will receive events emitted during load and execution. Typically
* obtained from {@link createFluidRunnerLogger}.
* @param options - Opaque, caller-defined string passed through to {@link IFluidFileConverter.execute}.
* @param timeout - Optional timeout in milliseconds. If the operation does not complete within this period
* the returned promise rejects. When omitted, no timeout is applied.
* @param disableNetworkFetch - When `true`, replaces `global.fetch` with an implementation that throws,
* ensuring the container load is fully serviced from the provided snapshot. Defaults to `false`.
* @returns The string result returned by {@link IFluidFileConverter.execute}.
*
* @legacy
* @beta
*/
export async function createContainerAndExecute(
export async function createFluidRunnerContainerAndExecute(
localOdspSnapshot: string | Uint8Array,
fluidFileConverter: IFluidFileConverter,
logger: ITelemetryLoggerExt,
baseLogger: ITelemetryBaseLogger,
options?: string,
timeout?: number,
disableNetworkFetch: boolean = false,
): Promise<string> {
const logger = createChildLogger({ logger: baseLogger });
const fn = async (): Promise<string> => {
if (disableNetworkFetch) {
global.fetch = async () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/tools/fluid-runner/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
/* eslint-disable import-x/no-internal-modules */
export type { ICodeLoaderBundle, IFluidFileConverter } from "./codeLoaderBundle.js";
export {
createContainerAndExecute,
createFluidRunnerContainerAndExecute,
Comment thread
kian-thompson marked this conversation as resolved.
exportFile,
type IExportFileResponse,
type IExportFileResponseSuccess,
Expand All @@ -15,11 +15,11 @@ export {
export { fluidRunner } from "./fluidRunner.js";
export {
OutputFormat,
type ITelemetryOptions,
type IFileLoggerTelemetryOptions,
type IFileLogger,
} from "./logger/fileLogger.js";
export {
createLogger,
createFluidRunnerLogger,
Comment thread
kian-thompson marked this conversation as resolved.
getTelemetryFileValidationError,
validateAndParseTelemetryOptions,
} from "./logger/loggerUtils.js";
Expand Down
8 changes: 5 additions & 3 deletions packages/tools/fluid-runner/src/logger/fileLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import type { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";

/**
* Contract for logger that writes telemetry to a file
* @internal
* @legacy
* @beta
*/
export interface IFileLogger extends ITelemetryBaseLogger {
/**
Expand All @@ -27,9 +28,10 @@ export enum OutputFormat {

/**
* Options to provide upon creation of IFileLogger
* @internal
* @legacy
* @beta
*/
export interface ITelemetryOptions {
export interface IFileLoggerTelemetryOptions {
/** Desired output format used to create a specific IFileLogger implementation */
outputFormat?: OutputFormat;

Expand Down
43 changes: 27 additions & 16 deletions packages/tools/fluid-runner/src/logger/loggerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,42 @@

import * as fs from "fs";

import {
type ITelemetryLoggerExt,
createChildLogger,
} from "@fluidframework/telemetry-utils/internal";
import type { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
import { createChildLogger } from "@fluidframework/telemetry-utils/internal";

import { CSVFileLogger } from "./csvFileLogger.js";
import { type IFileLogger, type ITelemetryOptions, OutputFormat } from "./fileLogger.js";
import {
type IFileLogger,
type IFileLoggerTelemetryOptions,
OutputFormat,
} from "./fileLogger.js";
import { JSONFileLogger } from "./jsonFileLogger.js";

/**
* Create an {@link @fluidframework/telemetry-utils#ITelemetryLoggerExt} wrapped around provided {@link IFileLogger}.
* Create an {@link @fluidframework/core-interfaces#ITelemetryBaseLogger} wrapped around an {@link IFileLogger}
* that writes telemetry events to the file at `filePath`.
*
* @remarks
* All telemetry events should be sent through the returned `logger`. The returned `fileLogger` is the
* underlying sink — its `close()` method must be called at the end of execution to flush any buffered
* events to disk.
*
* It is expected that all events be sent through the returned "logger" value.
* If `options.outputFormat` is not supplied, telemetry is written as JSON. Use {@link OutputFormat.CSV}
* to write CSV instead. See {@link IFileLoggerTelemetryOptions} for supported options including default properties
* applied to every event and flush batching.
*
* The "fileLogger" value should have its "close()" method called at the end of execution.
* @param filePath - Path to the file telemetry will be written to. Must not already exist.
* @param options - Optional telemetry configuration. See {@link IFileLoggerTelemetryOptions}.
* @returns The wrapped telemetry logger to send events through, and the underlying `IFileLogger`
* which must be closed when telemetry collection is finished.
*
* Note: if an output format is not supplied, default is JSON.
*
* @returns Both the `IFileLogger` implementation and `ITelemetryLoggerExt` wrapper to be called.
* @internal
* @legacy
* @beta
*/
export function createLogger(
export function createFluidRunnerLogger(
filePath: string,
options?: ITelemetryOptions,
): { logger: ITelemetryLoggerExt; fileLogger: IFileLogger } {
options?: IFileLoggerTelemetryOptions,
): { logger: ITelemetryBaseLogger; fileLogger: IFileLogger } {
const fileLogger =
options?.outputFormat === OutputFormat.CSV
? new CSVFileLogger(filePath, options?.eventsPerFlush, options?.defaultProps)
Expand Down Expand Up @@ -73,7 +82,9 @@ export function validateAndParseTelemetryOptions(
format?: string,
props?: (string | number)[],
eventsPerFlush?: number,
): { success: false; error: string } | { success: true; telemetryOptions: ITelemetryOptions } {
):
| { success: false; error: string }
| { success: true; telemetryOptions: IFileLoggerTelemetryOptions } {
let outputFormat: OutputFormat | undefined;
const defaultProps: Record<string, string | number> = {};

Expand Down
24 changes: 17 additions & 7 deletions packages/tools/fluid-runner/src/parseBundleAndExportFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@
import * as fs from "node:fs";
import * as path from "node:path";

import { PerformanceEvent } from "@fluidframework/telemetry-utils/internal";
import { createChildLogger, PerformanceEvent } from "@fluidframework/telemetry-utils/internal";

import { isCodeLoaderBundle, isFluidFileConverter } from "./codeLoaderBundle.js";
import { type IExportFileResponse, createContainerAndExecute } from "./exportFile.js";
import {
type IExportFileResponse,
createFluidRunnerContainerAndExecute,
} from "./exportFile.js";
/* eslint-disable import-x/no-internal-modules */
import type { ITelemetryOptions } from "./logger/fileLogger.js";
import { createLogger, getTelemetryFileValidationError } from "./logger/loggerUtils.js";
import type { IFileLoggerTelemetryOptions } from "./logger/fileLogger.js";
import {
createFluidRunnerLogger,
getTelemetryFileValidationError,
} from "./logger/loggerUtils.js";
/* eslint-enable import-x/no-internal-modules */
import { getArgsValidationError, getSnapshotFileContent } from "./utils.js";

Expand All @@ -29,7 +35,7 @@ export async function parseBundleAndExportFile(
outputFile: string,
telemetryFile: string,
options?: string,
telemetryOptions?: ITelemetryOptions,
telemetryOptions?: IFileLoggerTelemetryOptions,
timeout?: number,
disableNetworkFetch?: boolean,
): Promise<IExportFileResponse> {
Expand All @@ -38,7 +44,11 @@ export async function parseBundleAndExportFile(
const eventName = clientArgsValidationError;
return { success: false, eventName, errorMessage: telemetryArgError };
}
const { fileLogger, logger } = createLogger(telemetryFile, telemetryOptions);
const { fileLogger, logger: baseLogger } = createFluidRunnerLogger(
telemetryFile,
telemetryOptions,
);
const logger = createChildLogger({ logger: baseLogger });

try {
return await PerformanceEvent.timedExecAsync(
Expand Down Expand Up @@ -76,7 +86,7 @@ export async function parseBundleAndExportFile(

fs.writeFileSync(
outputFile,
await createContainerAndExecute(
await createFluidRunnerContainerAndExecute(
getSnapshotFileContent(inputFile),
fluidExport,
logger,
Expand Down
4 changes: 2 additions & 2 deletions packages/tools/fluid-runner/src/test/exportFile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import path from "path";
import { MockLogger } from "@fluidframework/telemetry-utils/internal";

/* eslint-disable import-x/no-internal-modules */
import { createContainerAndExecute, exportFile } from "../exportFile.js";
import { createFluidRunnerContainerAndExecute, exportFile } from "../exportFile.js";
import { getSnapshotFileContent } from "../utils.js";

import { _dirname } from "./dirname.cjs";
Expand Down Expand Up @@ -56,7 +56,7 @@ describe("exportFile", () => {
});

it("Execution result is correct", async () => {
const result = await createContainerAndExecute(
const result = await createFluidRunnerContainerAndExecute(
getSnapshotFileContent(path.join(snapshotFolder, snapshotFileName)),
fluidExport,
new MockLogger().toTelemetryLogger(),
Expand Down
6 changes: 3 additions & 3 deletions packages/tools/fluid-runner/src/test/loggerUtils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import path from "path";
/* eslint-disable import-x/no-internal-modules */
import { OutputFormat } from "../logger/fileLogger.js";
import {
createLogger,
createFluidRunnerLogger,
getTelemetryFileValidationError,
validateAndParseTelemetryOptions,
} from "../logger/loggerUtils.js";
Expand Down Expand Up @@ -173,10 +173,10 @@ describe("logger utils", () => {
});
});

describe("createLogger", () => {
describe("createFluidRunnerLogger", () => {
[-1, 0, 1, 25].forEach((eventsPerFlush) => {
it(`sets eventsPerFlush [${eventsPerFlush}] properly`, () => {
const { fileLogger } = createLogger("fake/path", {
const { fileLogger } = createFluidRunnerLogger("fake/path", {
outputFormat: OutputFormat.CSV,
eventsPerFlush,
});
Expand Down
Loading