diff --git a/.changeset/expose-fluid-runner-helpers.md b/.changeset/expose-fluid-runner-helpers.md new file mode 100644 index 000000000000..baaa6eccd879 --- /dev/null +++ b/.changeset/expose-fluid-runner-helpers.md @@ -0,0 +1,16 @@ +--- +"@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`. + +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`. + +For back-compatibility, the previous `@internal` names are retained as `@deprecated` aliases: + +- `createContainerAndExecute` → use `createFluidRunnerContainerAndExecute` +- `createLogger` → use `createFluidRunnerLogger` +- `ITelemetryOptions` → use `IFileLoggerTelemetryOptions` + diff --git a/packages/tools/fluid-runner/api-report/fluid-runner.legacy.beta.api.md b/packages/tools/fluid-runner/api-report/fluid-runner.legacy.beta.api.md index ebc8f8ff21c1..d716f800945e 100644 --- a/packages/tools/fluid-runner/api-report/fluid-runner.legacy.beta.api.md +++ b/packages/tools/fluid-runner/api-report/fluid-runner.legacy.beta.api.md @@ -4,6 +4,15 @@ ```ts +// @beta @legacy +export function createFluidRunnerContainerAndExecute(localOdspSnapshot: string | Uint8Array, fluidFileConverter: IFluidFileConverter, baseLogger: ITelemetryBaseLogger, options?: string, timeout?: number, disableNetworkFetch?: boolean): Promise; + +// @beta @legacy +export function createFluidRunnerLogger(filePath: string, options?: IFileLoggerTelemetryOptions): { + logger: ITelemetryBaseLogger; + fileLogger: IFileLogger; +}; + // @beta @legacy (undocumented) export type IExportFileResponse = IExportFileResponseSuccess | IExportFileResponseFailure; @@ -25,6 +34,18 @@ export interface IExportFileResponseSuccess { success: true; } +// @beta @legacy +export interface IFileLogger extends ITelemetryBaseLogger { + close(): Promise; +} + +// @beta @legacy +export interface IFileLoggerTelemetryOptions { + defaultProps?: Record; + eventsPerFlush?: number; + outputFormat?: OutputFormat; +} + // @beta @legacy export interface IFluidFileConverter { execute(container: IContainer, options?: string): Promise; diff --git a/packages/tools/fluid-runner/src/exportFile.ts b/packages/tools/fluid-runner/src/exportFile.ts index 8e019d881041..39cffaa459fc 100644 --- a/packages/tools/fluid-runner/src/exportFile.ts +++ b/packages/tools/fluid-runner/src/exportFile.ts @@ -11,17 +11,23 @@ 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 TelemetryLoggerExt, PerformanceEvent, + createChildLogger, } 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 { + // eslint-disable-next-line import-x/no-deprecated + createLogger, + getTelemetryFileValidationError, +} from "./logger/loggerUtils.js"; import { getArgsValidationError, getSnapshotFileContent, timeoutPromise } from "./utils.js"; /* eslint-enable import-x/no-internal-modules */ @@ -50,7 +56,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( @@ -59,7 +66,7 @@ export async function exportFile( outputFile: string, telemetryFile: string, options?: string, - telemetryOptions?: ITelemetryOptions, + telemetryOptions?: IFileLoggerTelemetryOptions, timeout?: number, disableNetworkFetch?: boolean, ): Promise { @@ -68,6 +75,7 @@ export async function exportFile( const eventName = clientArgsValidationError; return { success: false, eventName, errorMessage: telemetryArgError }; } + // eslint-disable-next-line import-x/no-deprecated const { fileLogger, logger } = createLogger(telemetryFile, telemetryOptions); try { @@ -106,9 +114,53 @@ export async function exportFile( } } +/** + * 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 createFluidRunnerContainerAndExecute( + localOdspSnapshot: string | Uint8Array, + fluidFileConverter: IFluidFileConverter, + baseLogger: ITelemetryBaseLogger, + options?: string, + timeout?: number, + disableNetworkFetch?: boolean, +): Promise { + const logger = createChildLogger({ logger: baseLogger }); + return createContainerAndExecute( + localOdspSnapshot, + fluidFileConverter, + logger, + options, + timeout, + disableNetworkFetch, + ); +} + /** * Create the container based on an ODSP snapshot and execute code on it * @returns result of execution + * @deprecated Use {@link createFluidRunnerContainerAndExecute}. * @internal */ export async function createContainerAndExecute( diff --git a/packages/tools/fluid-runner/src/index.ts b/packages/tools/fluid-runner/src/index.ts index a5cc0a08e213..e2c2bdb291d6 100644 --- a/packages/tools/fluid-runner/src/index.ts +++ b/packages/tools/fluid-runner/src/index.ts @@ -7,6 +7,7 @@ export type { ICodeLoaderBundle, IFluidFileConverter } from "./codeLoaderBundle.js"; export { createContainerAndExecute, + createFluidRunnerContainerAndExecute, exportFile, type IExportFileResponse, type IExportFileResponseSuccess, @@ -15,10 +16,12 @@ export { export { fluidRunner } from "./fluidRunner.js"; export { OutputFormat, - type ITelemetryOptions, + type IFileLoggerTelemetryOptions, type IFileLogger, + type ITelemetryOptions, } from "./logger/fileLogger.js"; export { + createFluidRunnerLogger, createLogger, getTelemetryFileValidationError, validateAndParseTelemetryOptions, diff --git a/packages/tools/fluid-runner/src/logger/fileLogger.ts b/packages/tools/fluid-runner/src/logger/fileLogger.ts index 597f5f080411..fcca8cfccab9 100644 --- a/packages/tools/fluid-runner/src/logger/fileLogger.ts +++ b/packages/tools/fluid-runner/src/logger/fileLogger.ts @@ -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 { /** @@ -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; @@ -47,3 +49,10 @@ export interface ITelemetryOptions { /** Number of telemetry events per flush to telemetry file */ eventsPerFlush?: number; } + +/** + * Options to provide upon creation of IFileLogger + * @deprecated Use {@link IFileLoggerTelemetryOptions}. + * @internal + */ +export type ITelemetryOptions = IFileLoggerTelemetryOptions; diff --git a/packages/tools/fluid-runner/src/logger/loggerUtils.ts b/packages/tools/fluid-runner/src/logger/loggerUtils.ts index 01ed21b6a5d4..e999e197016d 100644 --- a/packages/tools/fluid-runner/src/logger/loggerUtils.ts +++ b/packages/tools/fluid-runner/src/logger/loggerUtils.ts @@ -5,15 +5,49 @@ import * as fs from "fs"; +import type { ITelemetryBaseLogger } from "@fluidframework/core-interfaces"; import { type TelemetryLoggerExt, 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, + // eslint-disable-next-line import-x/no-deprecated + type ITelemetryOptions, + OutputFormat, +} from "./fileLogger.js"; import { JSONFileLogger } from "./jsonFileLogger.js"; +/** + * Create a telemetry logger wrapped around an {@link IFileLogger} that writes to the given file path. + * + * @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. + * + * 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. + * + * @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. + * + * @legacy + * @beta + */ +export function createFluidRunnerLogger( + filePath: string, + options?: IFileLoggerTelemetryOptions, +): { logger: ITelemetryBaseLogger; fileLogger: IFileLogger } { + return createLogger(filePath, options); +} + /** * Create an {@link @fluidframework/telemetry-utils#TelemetryLoggerExt} wrapped around provided {@link IFileLogger}. * @@ -26,10 +60,13 @@ import { JSONFileLogger } from "./jsonFileLogger.js"; * Note: if an output format is not supplied, default is JSON. * * @returns Both the `IFileLogger` implementation and `TelemetryLoggerExt` wrapper to be called. + * + * @deprecated Use {@link createFluidRunnerLogger}. * @internal */ export function createLogger( filePath: string, + // eslint-disable-next-line import-x/no-deprecated options?: ITelemetryOptions, ): { logger: TelemetryLoggerExt; fileLogger: IFileLogger } { const fileLogger = @@ -73,7 +110,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 = {}; diff --git a/packages/tools/fluid-runner/src/parseBundleAndExportFile.ts b/packages/tools/fluid-runner/src/parseBundleAndExportFile.ts index 509a62464dff..f3776be69af3 100644 --- a/packages/tools/fluid-runner/src/parseBundleAndExportFile.ts +++ b/packages/tools/fluid-runner/src/parseBundleAndExportFile.ts @@ -9,10 +9,18 @@ import * as path from "node:path"; import { PerformanceEvent } from "@fluidframework/telemetry-utils/internal"; import { isCodeLoaderBundle, isFluidFileConverter } from "./codeLoaderBundle.js"; -import { type IExportFileResponse, createContainerAndExecute } from "./exportFile.js"; +import { + type IExportFileResponse, + // eslint-disable-next-line import-x/no-deprecated + createContainerAndExecute, +} 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 { + // eslint-disable-next-line import-x/no-deprecated + createLogger, + getTelemetryFileValidationError, +} from "./logger/loggerUtils.js"; /* eslint-enable import-x/no-internal-modules */ import { getArgsValidationError, getSnapshotFileContent } from "./utils.js"; @@ -29,7 +37,7 @@ export async function parseBundleAndExportFile( outputFile: string, telemetryFile: string, options?: string, - telemetryOptions?: ITelemetryOptions, + telemetryOptions?: IFileLoggerTelemetryOptions, timeout?: number, disableNetworkFetch?: boolean, ): Promise { @@ -38,6 +46,7 @@ export async function parseBundleAndExportFile( const eventName = clientArgsValidationError; return { success: false, eventName, errorMessage: telemetryArgError }; } + // eslint-disable-next-line import-x/no-deprecated const { fileLogger, logger } = createLogger(telemetryFile, telemetryOptions); try { @@ -76,6 +85,7 @@ export async function parseBundleAndExportFile( fs.writeFileSync( outputFile, + // eslint-disable-next-line import-x/no-deprecated await createContainerAndExecute( getSnapshotFileContent(inputFile), fluidExport, diff --git a/packages/tools/fluid-runner/src/test/exportFile.spec.ts b/packages/tools/fluid-runner/src/test/exportFile.spec.ts index caa0c732c3d0..913575a30573 100644 --- a/packages/tools/fluid-runner/src/test/exportFile.spec.ts +++ b/packages/tools/fluid-runner/src/test/exportFile.spec.ts @@ -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"; @@ -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(), diff --git a/packages/tools/fluid-runner/src/test/loggerUtils.spec.ts b/packages/tools/fluid-runner/src/test/loggerUtils.spec.ts index 4a0332a56516..c83c7dd9b1e3 100644 --- a/packages/tools/fluid-runner/src/test/loggerUtils.spec.ts +++ b/packages/tools/fluid-runner/src/test/loggerUtils.spec.ts @@ -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"; @@ -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, });